(2018/10/02 21:16), Etsuro Fujita wrote:
Attached is an updated
version of the patch. Changes:
That patch conflicts the recent executor changes, so I'm attaching a
rebased patch, in which I also added a fast path to
add_params_to_result_rel and did some comment editing for consistency.
I'll add this to the next CF so that it does not get lost.
Best regards,
Etsuro Fujita
*** a/contrib/postgres_fdw/deparse.c
--- b/contrib/postgres_fdw/deparse.c
***************
*** 130,135 **** static void deparseTargetList(StringInfo buf,
--- 130,136 ----
Relation rel,
bool is_returning,
Bitmapset *attrs_used,
+ bool tableoid_needed,
bool qualify_col,
List **retrieved_attrs);
static void deparseExplicitTargetList(List *tlist,
***************
*** 901,906 **** build_tlist_to_deparse(RelOptInfo *foreignrel)
--- 902,926 ----
PVC_RECURSE_PLACEHOLDERS));
}
+ /* Also, add the Param representing the remote table OID, if it exists. */
+ if (fpinfo->tableoid_param)
+ {
+ TargetEntry *tle;
+
+ /*
+ * Core code should have contained the Param in the given relation's
+ * reltarget.
+ */
+ Assert(list_member(foreignrel->reltarget->exprs,
+ fpinfo->tableoid_param));
+
+ tle = makeTargetEntry((Expr *) copyObject(fpinfo->tableoid_param),
+ list_length(tlist) + 1,
+ NULL,
+ false);
+ tlist = lappend(tlist, tle);
+ }
+
return tlist;
}
***************
*** 1052,1058 **** deparseSelectSql(List *tlist, bool is_subquery, List **retrieved_attrs,
Relation rel = heap_open(rte->relid, NoLock);
deparseTargetList(buf, rte, foreignrel->relid, rel, false,
! fpinfo->attrs_used, false, retrieved_attrs);
heap_close(rel, NoLock);
}
}
--- 1072,1080 ----
Relation rel = heap_open(rte->relid, NoLock);
deparseTargetList(buf, rte, foreignrel->relid, rel, false,
! fpinfo->attrs_used,
! fpinfo->tableoid_param ? true : false,
! false, retrieved_attrs);
heap_close(rel, NoLock);
}
}
***************
*** 1093,1098 **** deparseFromExpr(List *quals, deparse_expr_cxt *context)
--- 1115,1122 ----
* This is used for both SELECT and RETURNING targetlists; the is_returning
* parameter is true only for a RETURNING targetlist.
*
+ * For SELECT, the target list contains remote tableoid if tableoid_needed.
+ *
* The tlist text is appended to buf, and we also create an integer List
* of the columns being retrieved, which is returned to *retrieved_attrs.
*
***************
*** 1105,1110 **** deparseTargetList(StringInfo buf,
--- 1129,1135 ----
Relation rel,
bool is_returning,
Bitmapset *attrs_used,
+ bool tableoid_needed,
bool qualify_col,
List **retrieved_attrs)
{
***************
*** 1146,1152 **** deparseTargetList(StringInfo buf,
/*
* Add ctid and oid if needed. We currently don't support retrieving any
! * other system columns.
*/
if (bms_is_member(SelfItemPointerAttributeNumber - FirstLowInvalidHeapAttributeNumber,
attrs_used))
--- 1171,1177 ----
/*
* Add ctid and oid if needed. We currently don't support retrieving any
! * other system columns, except tableoid, which is retrieved if required.
*/
if (bms_is_member(SelfItemPointerAttributeNumber - FirstLowInvalidHeapAttributeNumber,
attrs_used))
***************
*** 1180,1185 **** deparseTargetList(StringInfo buf,
--- 1205,1224 ----
*retrieved_attrs = lappend_int(*retrieved_attrs,
ObjectIdAttributeNumber);
}
+ if (tableoid_needed)
+ {
+ Assert(bms_is_member(SelfItemPointerAttributeNumber - FirstLowInvalidHeapAttributeNumber,
+ attrs_used));
+ Assert(!first);
+ Assert(!is_returning);
+ Assert(!qualify_col);
+
+ appendStringInfoString(buf, ", ");
+ appendStringInfoString(buf, "tableoid");
+
+ *retrieved_attrs = lappend_int(*retrieved_attrs,
+ TableOidAttributeNumber);
+ }
/* Don't generate bad syntax if no undropped columns */
if (first && !is_returning)
***************
*** 1728,1734 **** deparseUpdateSql(StringInfo buf, RangeTblEntry *rte,
deparseRelation(buf, rel);
appendStringInfoString(buf, " SET ");
! pindex = 2; /* ctid is always the first param */
first = true;
foreach(lc, targetAttrs)
{
--- 1767,1774 ----
deparseRelation(buf, rel);
appendStringInfoString(buf, " SET ");
! pindex = 3; /* ctid and tableoid are always the two
! * leading params */
first = true;
foreach(lc, targetAttrs)
{
***************
*** 1742,1748 **** deparseUpdateSql(StringInfo buf, RangeTblEntry *rte,
appendStringInfo(buf, " = $%d", pindex);
pindex++;
}
! appendStringInfoString(buf, " WHERE ctid = $1");
deparseReturningList(buf, rte, rtindex, rel,
rel->trigdesc && rel->trigdesc->trig_update_after_row,
--- 1782,1788 ----
appendStringInfo(buf, " = $%d", pindex);
pindex++;
}
! appendStringInfoString(buf, " WHERE ctid = $1 AND tableoid = $2");
deparseReturningList(buf, rte, rtindex, rel,
rel->trigdesc && rel->trigdesc->trig_update_after_row,
***************
*** 1858,1864 **** deparseDeleteSql(StringInfo buf, RangeTblEntry *rte,
{
appendStringInfoString(buf, "DELETE FROM ");
deparseRelation(buf, rel);
! appendStringInfoString(buf, " WHERE ctid = $1");
deparseReturningList(buf, rte, rtindex, rel,
rel->trigdesc && rel->trigdesc->trig_delete_after_row,
--- 1898,1904 ----
{
appendStringInfoString(buf, "DELETE FROM ");
deparseRelation(buf, rel);
! appendStringInfoString(buf, " WHERE ctid = $1 AND tableoid = $2");
deparseReturningList(buf, rte, rtindex, rel,
rel->trigdesc && rel->trigdesc->trig_delete_after_row,
***************
*** 1974,1980 **** deparseReturningList(StringInfo buf, RangeTblEntry *rte,
if (attrs_used != NULL)
deparseTargetList(buf, rte, rtindex, rel, true, attrs_used, false,
! retrieved_attrs);
else
*retrieved_attrs = NIL;
}
--- 2014,2020 ----
if (attrs_used != NULL)
deparseTargetList(buf, rte, rtindex, rel, true, attrs_used, false,
! false, retrieved_attrs);
else
*retrieved_attrs = NIL;
}
***************
*** 2147,2154 **** deparseColumnRef(StringInfo buf, int varno, int varattno, RangeTblEntry *rte,
}
appendStringInfoString(buf, "ROW(");
! deparseTargetList(buf, rte, varno, rel, false, attrs_used, qualify_col,
! &retrieved_attrs);
appendStringInfoChar(buf, ')');
/* Complete the CASE WHEN statement started above. */
--- 2187,2194 ----
}
appendStringInfoString(buf, "ROW(");
! deparseTargetList(buf, rte, varno, rel, false, attrs_used, false,
! qualify_col, &retrieved_attrs);
appendStringInfoChar(buf, ')');
/* Complete the CASE WHEN statement started above. */
***************
*** 2514,2519 **** deparseConst(Const *node, deparse_expr_cxt *context, int showtype)
--- 2554,2575 ----
static void
deparseParam(Param *node, deparse_expr_cxt *context)
{
+ PgFdwRelationInfo *fpinfo = (PgFdwRelationInfo *) context->foreignrel->fdw_private;
+
+ /*
+ * If the Param is the one representing the remote table OID, the value
+ * needs to be produced; fetch the remote table OID, instead.
+ */
+ if (equal(node, (Node *) fpinfo->tableoid_param))
+ {
+ Assert(bms_is_member(context->root->parse->resultRelation,
+ context->foreignrel->relids));
+ Assert(bms_membership(context->foreignrel->relids) == BMS_MULTIPLE);
+ ADD_REL_QUALIFIER(context->buf, context->root->parse->resultRelation);
+ appendStringInfoString(context->buf, "tableoid");
+ return;
+ }
+
if (context->params_list)
{
int pindex = 0;
*** a/contrib/postgres_fdw/expected/postgres_fdw.out
--- b/contrib/postgres_fdw/expected/postgres_fdw.out
***************
*** 5497,5511 **** INSERT INTO ft2 (c1,c2,c3)
SELECT id, id % 10, to_char(id, 'FM00000') FROM generate_series(2001, 2010) id;
EXPLAIN (verbose, costs off)
UPDATE ft2 SET c3 = 'bar' WHERE postgres_fdw_abs(c1) > 2000 RETURNING *; -- can't be pushed down
! QUERY PLAN
! ----------------------------------------------------------------------------------------------------------
Update on public.ft2
Output: c1, c2, c3, c4, c5, c6, c7, c8
! Remote SQL: UPDATE "S 1"."T 1" SET c3 = $2 WHERE ctid = $1 RETURNING "C 1", c2, c3, c4, c5, c6, c7, c8
-> Foreign Scan on public.ft2
! Output: c1, c2, NULL::integer, 'bar'::text, c4, c5, c6, c7, c8, ctid
Filter: (postgres_fdw_abs(ft2.c1) > 2000)
! Remote SQL: SELECT "C 1", c2, c4, c5, c6, c7, c8, ctid FROM "S 1"."T 1" FOR UPDATE
(7 rows)
UPDATE ft2 SET c3 = 'bar' WHERE postgres_fdw_abs(c1) > 2000 RETURNING *;
--- 5497,5511 ----
SELECT id, id % 10, to_char(id, 'FM00000') FROM generate_series(2001, 2010) id;
EXPLAIN (verbose, costs off)
UPDATE ft2 SET c3 = 'bar' WHERE postgres_fdw_abs(c1) > 2000 RETURNING *; -- can't be pushed down
! QUERY PLAN
! ----------------------------------------------------------------------------------------------------------------------------
Update on public.ft2
Output: c1, c2, c3, c4, c5, c6, c7, c8
! Remote SQL: UPDATE "S 1"."T 1" SET c3 = $3 WHERE ctid = $1 AND tableoid = $2 RETURNING "C 1", c2, c3, c4, c5, c6, c7, c8
-> Foreign Scan on public.ft2
! Output: c1, c2, NULL::integer, 'bar'::text, c4, c5, c6, c7, c8, ctid, $0
Filter: (postgres_fdw_abs(ft2.c1) > 2000)
! Remote SQL: SELECT "C 1", c2, c4, c5, c6, c7, c8, ctid, tableoid FROM "S 1"."T 1" FOR UPDATE
(7 rows)
UPDATE ft2 SET c3 = 'bar' WHERE postgres_fdw_abs(c1) > 2000 RETURNING *;
***************
*** 5532,5544 **** UPDATE ft2 SET c3 = 'baz'
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Update on public.ft2
Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3
! Remote SQL: UPDATE "S 1"."T 1" SET c3 = $2 WHERE ctid = $1 RETURNING "C 1", c2, c3, c4, c5, c6, c7, c8
-> Nested Loop
! Output: ft2.c1, ft2.c2, NULL::integer, 'baz'::text, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.ctid, ft4.*, ft5.*, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3
Join Filter: (ft2.c2 === ft4.c1)
-> Foreign Scan on public.ft2
! Output: ft2.c1, ft2.c2, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.ctid
! Remote SQL: SELECT "C 1", c2, c4, c5, c6, c7, c8, ctid FROM "S 1"."T 1" WHERE (("C 1" > 2000)) FOR UPDATE
-> Foreign Scan
Output: ft4.*, ft4.c1, ft4.c2, ft4.c3, ft5.*, ft5.c1, ft5.c2, ft5.c3
Relations: (public.ft4) INNER JOIN (public.ft5)
--- 5532,5544 ----
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Update on public.ft2
Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3
! Remote SQL: UPDATE "S 1"."T 1" SET c3 = $3 WHERE ctid = $1 AND tableoid = $2 RETURNING "C 1", c2, c3, c4, c5, c6, c7, c8
-> Nested Loop
! Output: ft2.c1, ft2.c2, NULL::integer, 'baz'::text, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.ctid, ($0), ft4.*, ft5.*, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3
Join Filter: (ft2.c2 === ft4.c1)
-> Foreign Scan on public.ft2
! Output: ft2.c1, ft2.c2, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.ctid, $0
! Remote SQL: SELECT "C 1", c2, c4, c5, c6, c7, c8, ctid, tableoid FROM "S 1"."T 1" WHERE (("C 1" > 2000)) FOR UPDATE
-> Foreign Scan
Output: ft4.*, ft4.c1, ft4.c2, ft4.c3, ft5.*, ft5.c1, ft5.c2, ft5.c3
Relations: (public.ft4) INNER JOIN (public.ft5)
***************
*** 5570,5593 **** DELETE FROM ft2
USING ft4 INNER JOIN ft5 ON (ft4.c1 === ft5.c1)
WHERE ft2.c1 > 2000 AND ft2.c2 = ft4.c1
RETURNING ft2.c1, ft2.c2, ft2.c3; -- can't be pushed down
! QUERY PLAN
! ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Delete on public.ft2
Output: ft2.c1, ft2.c2, ft2.c3
! Remote SQL: DELETE FROM "S 1"."T 1" WHERE ctid = $1 RETURNING "C 1", c2, c3
-> Foreign Scan
! Output: ft2.ctid, ft4.*, ft5.*
Filter: (ft4.c1 === ft5.c1)
Relations: ((public.ft2) INNER JOIN (public.ft4)) INNER JOIN (public.ft5)
! Remote SQL: SELECT r1.ctid, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2.c1, r2.c2, r2.c3) END, CASE WHEN (r3.*)::text IS NOT NULL THEN ROW(r3.c1, r3.c2, r3.c3) END, r2.c1, r3.c1 FROM (("S 1"."T 1" r1 INNER JOIN "S 1"."T 3" r2 ON (((r1.c2 = r2.c1)) AND ((r1."C 1" > 2000)))) INNER JOIN "S 1"."T 4" r3 ON (TRUE)) FOR UPDATE OF r1
-> Nested Loop
! Output: ft2.ctid, ft4.*, ft5.*, ft4.c1, ft5.c1
-> Nested Loop
! Output: ft2.ctid, ft4.*, ft4.c1
Join Filter: (ft2.c2 = ft4.c1)
-> Foreign Scan on public.ft2
! Output: ft2.ctid, ft2.c2
! Remote SQL: SELECT c2, ctid FROM "S 1"."T 1" WHERE (("C 1" > 2000)) FOR UPDATE
-> Foreign Scan on public.ft4
Output: ft4.*, ft4.c1
Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3"
--- 5570,5593 ----
USING ft4 INNER JOIN ft5 ON (ft4.c1 === ft5.c1)
WHERE ft2.c1 > 2000 AND ft2.c2 = ft4.c1
RETURNING ft2.c1, ft2.c2, ft2.c3; -- can't be pushed down
! QUERY PLAN
! -----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Delete on public.ft2
Output: ft2.c1, ft2.c2, ft2.c3
! Remote SQL: DELETE FROM "S 1"."T 1" WHERE ctid = $1 AND tableoid = $2 RETURNING "C 1", c2, c3
-> Foreign Scan
! Output: ft2.ctid, ($0), ft4.*, ft5.*
Filter: (ft4.c1 === ft5.c1)
Relations: ((public.ft2) INNER JOIN (public.ft4)) INNER JOIN (public.ft5)
! Remote SQL: SELECT r1.ctid, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2.c1, r2.c2, r2.c3) END, CASE WHEN (r3.*)::text IS NOT NULL THEN ROW(r3.c1, r3.c2, r3.c3) END, r2.c1, r3.c1, r1.tableoid FROM (("S 1"."T 1" r1 INNER JOIN "S 1"."T 3" r2 ON (((r1.c2 = r2.c1)) AND ((r1."C 1" > 2000)))) INNER JOIN "S 1"."T 4" r3 ON (TRUE)) FOR UPDATE OF r1
-> Nested Loop
! Output: ft2.ctid, ft4.*, ft5.*, ft4.c1, ft5.c1, ($0)
-> Nested Loop
! Output: ft2.ctid, ($0), ft4.*, ft4.c1
Join Filter: (ft2.c2 = ft4.c1)
-> Foreign Scan on public.ft2
! Output: ft2.ctid, $0, ft2.c2
! Remote SQL: SELECT c2, ctid, tableoid FROM "S 1"."T 1" WHERE (("C 1" > 2000)) FOR UPDATE
-> Foreign Scan on public.ft4
Output: ft4.*, ft4.c1
Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3"
***************
*** 6088,6093 **** SELECT * FROM ft1 ORDER BY c6 ASC NULLS FIRST, c1 OFFSET 15 LIMIT 10;
--- 6088,6174 ----
40 | 42 | 00040_trig_update | Tue Feb 10 00:00:00 1970 PST | Tue Feb 10 00:00:00 1970 | 0 | 0 | foo
(10 rows)
+ -- Test UPDATE/DELETE in the case where the remote target is
+ -- an inheritance tree
+ CREATE TABLE p1 (a int, b int);
+ CREATE TABLE c1 (LIKE p1) INHERITS (p1);
+ NOTICE: merging column "a" with inherited definition
+ NOTICE: merging column "b" with inherited definition
+ CREATE TABLE c2 (LIKE p1) INHERITS (p1);
+ NOTICE: merging column "a" with inherited definition
+ NOTICE: merging column "b" with inherited definition
+ CREATE FOREIGN TABLE fp1 (a int, b int)
+ SERVER loopback OPTIONS (table_name 'p1');
+ INSERT INTO c1 VALUES (0, 1);
+ INSERT INTO c2 VALUES (1, 1);
+ SELECT tableoid::regclass, ctid, * FROM fp1;
+ tableoid | ctid | a | b
+ ----------+-------+---+---
+ fp1 | (0,1) | 0 | 1
+ fp1 | (0,1) | 1 | 1
+ (2 rows)
+
+ -- Update statement should not be pushed down to the remote side
+ EXPLAIN (VERBOSE, COSTS OFF)
+ UPDATE fp1 SET b = b + 1 WHERE a = 0 AND random() <= 1;
+ QUERY PLAN
+ -------------------------------------------------------------------------------------------
+ Update on public.fp1
+ Remote SQL: UPDATE public.p1 SET b = $3 WHERE ctid = $1 AND tableoid = $2
+ -> Foreign Scan on public.fp1
+ Output: a, (b + 1), ctid, $0
+ Filter: (random() <= '1'::double precision)
+ Remote SQL: SELECT a, b, ctid, tableoid FROM public.p1 WHERE ((a = 0)) FOR UPDATE
+ (6 rows)
+
+ UPDATE fp1 SET b = b + 1 WHERE a = 0 AND random() <= 1;
+ -- Only one tuple should be updated
+ SELECT tableoid::regclass, ctid, * FROM fp1;
+ tableoid | ctid | a | b
+ ----------+-------+---+---
+ fp1 | (0,2) | 0 | 2
+ fp1 | (0,1) | 1 | 1
+ (2 rows)
+
+ -- Recreate the table data
+ TRUNCATE c1;
+ TRUNCATE c2;
+ INSERT INTO c1 VALUES (0, 1);
+ INSERT INTO c2 VALUES (1, 1);
+ SELECT tableoid::regclass, ctid, * FROM fp1;
+ tableoid | ctid | a | b
+ ----------+-------+---+---
+ fp1 | (0,1) | 0 | 1
+ fp1 | (0,1) | 1 | 1
+ (2 rows)
+
+ -- Delete statement should not be pushed down to the remote side
+ EXPLAIN (VERBOSE, COSTS OFF)
+ DELETE FROM fp1 WHERE a = 1 AND random() <= 1;
+ QUERY PLAN
+ -------------------------------------------------------------------------------------
+ Delete on public.fp1
+ Remote SQL: DELETE FROM public.p1 WHERE ctid = $1 AND tableoid = $2
+ -> Foreign Scan on public.fp1
+ Output: ctid, $0
+ Filter: (random() <= '1'::double precision)
+ Remote SQL: SELECT ctid, tableoid FROM public.p1 WHERE ((a = 1)) FOR UPDATE
+ (6 rows)
+
+ DELETE FROM fp1 WHERE a = 1 AND random() <= 1;
+ -- Only one tuple should be deleted
+ SELECT tableoid::regclass, ctid, * FROM fp1;
+ tableoid | ctid | a | b
+ ----------+-------+---+---
+ fp1 | (0,1) | 0 | 1
+ (1 row)
+
+ -- cleanup
+ DROP FOREIGN TABLE fp1;
+ DROP TABLE p1 CASCADE;
+ NOTICE: drop cascades to 2 other objects
+ DETAIL: drop cascades to table c1
+ drop cascades to table c2
-- ===================================================================
-- test check constraints
-- ===================================================================
***************
*** 6229,6241 **** SELECT * FROM foreign_tbl;
EXPLAIN (VERBOSE, COSTS OFF)
UPDATE rw_view SET b = b + 5;
! QUERY PLAN
! ---------------------------------------------------------------------------------------
Update on public.foreign_tbl
! Remote SQL: UPDATE public.base_tbl SET b = $2 WHERE ctid = $1 RETURNING a, b
-> Foreign Scan on public.foreign_tbl
! Output: foreign_tbl.a, (foreign_tbl.b + 5), foreign_tbl.ctid
! Remote SQL: SELECT a, b, ctid FROM public.base_tbl WHERE ((a < b)) FOR UPDATE
(5 rows)
UPDATE rw_view SET b = b + 5; -- should fail
--- 6310,6322 ----
EXPLAIN (VERBOSE, COSTS OFF)
UPDATE rw_view SET b = b + 5;
! QUERY PLAN
! --------------------------------------------------------------------------------------------------
Update on public.foreign_tbl
! Remote SQL: UPDATE public.base_tbl SET b = $3 WHERE ctid = $1 AND tableoid = $2 RETURNING a, b
-> Foreign Scan on public.foreign_tbl
! Output: foreign_tbl.a, (foreign_tbl.b + 5), foreign_tbl.ctid, $0
! Remote SQL: SELECT a, b, ctid, tableoid FROM public.base_tbl WHERE ((a < b)) FOR UPDATE
(5 rows)
UPDATE rw_view SET b = b + 5; -- should fail
***************
*** 6243,6255 **** ERROR: new row violates check option for view "rw_view"
DETAIL: Failing row contains (20, 20).
EXPLAIN (VERBOSE, COSTS OFF)
UPDATE rw_view SET b = b + 15;
! QUERY PLAN
! ---------------------------------------------------------------------------------------
Update on public.foreign_tbl
! Remote SQL: UPDATE public.base_tbl SET b = $2 WHERE ctid = $1 RETURNING a, b
-> Foreign Scan on public.foreign_tbl
! Output: foreign_tbl.a, (foreign_tbl.b + 15), foreign_tbl.ctid
! Remote SQL: SELECT a, b, ctid FROM public.base_tbl WHERE ((a < b)) FOR UPDATE
(5 rows)
UPDATE rw_view SET b = b + 15; -- ok
--- 6324,6336 ----
DETAIL: Failing row contains (20, 20).
EXPLAIN (VERBOSE, COSTS OFF)
UPDATE rw_view SET b = b + 15;
! QUERY PLAN
! --------------------------------------------------------------------------------------------------
Update on public.foreign_tbl
! Remote SQL: UPDATE public.base_tbl SET b = $3 WHERE ctid = $1 AND tableoid = $2 RETURNING a, b
-> Foreign Scan on public.foreign_tbl
! Output: foreign_tbl.a, (foreign_tbl.b + 15), foreign_tbl.ctid, $0
! Remote SQL: SELECT a, b, ctid, tableoid FROM public.base_tbl WHERE ((a < b)) FOR UPDATE
(5 rows)
UPDATE rw_view SET b = b + 15; -- ok
***************
*** 6316,6329 **** SELECT * FROM foreign_tbl;
EXPLAIN (VERBOSE, COSTS OFF)
UPDATE rw_view SET b = b + 5;
! QUERY PLAN
! ----------------------------------------------------------------------------------------
Update on public.parent_tbl
Foreign Update on public.foreign_tbl
! Remote SQL: UPDATE public.child_tbl SET b = $2 WHERE ctid = $1 RETURNING a, b
-> Foreign Scan on public.foreign_tbl
! Output: foreign_tbl.a, (foreign_tbl.b + 5), foreign_tbl.ctid
! Remote SQL: SELECT a, b, ctid FROM public.child_tbl WHERE ((a < b)) FOR UPDATE
(6 rows)
UPDATE rw_view SET b = b + 5; -- should fail
--- 6397,6410 ----
EXPLAIN (VERBOSE, COSTS OFF)
UPDATE rw_view SET b = b + 5;
! QUERY PLAN
! -----------------------------------------------------------------------------------------------------
Update on public.parent_tbl
Foreign Update on public.foreign_tbl
! Remote SQL: UPDATE public.child_tbl SET b = $3 WHERE ctid = $1 AND tableoid = $2 RETURNING a, b
-> Foreign Scan on public.foreign_tbl
! Output: foreign_tbl.a, (foreign_tbl.b + 5), foreign_tbl.ctid, $0
! Remote SQL: SELECT a, b, ctid, tableoid FROM public.child_tbl WHERE ((a < b)) FOR UPDATE
(6 rows)
UPDATE rw_view SET b = b + 5; -- should fail
***************
*** 6331,6344 **** ERROR: new row violates check option for view "rw_view"
DETAIL: Failing row contains (20, 20).
EXPLAIN (VERBOSE, COSTS OFF)
UPDATE rw_view SET b = b + 15;
! QUERY PLAN
! ----------------------------------------------------------------------------------------
Update on public.parent_tbl
Foreign Update on public.foreign_tbl
! Remote SQL: UPDATE public.child_tbl SET b = $2 WHERE ctid = $1 RETURNING a, b
-> Foreign Scan on public.foreign_tbl
! Output: foreign_tbl.a, (foreign_tbl.b + 15), foreign_tbl.ctid
! Remote SQL: SELECT a, b, ctid FROM public.child_tbl WHERE ((a < b)) FOR UPDATE
(6 rows)
UPDATE rw_view SET b = b + 15; -- ok
--- 6412,6425 ----
DETAIL: Failing row contains (20, 20).
EXPLAIN (VERBOSE, COSTS OFF)
UPDATE rw_view SET b = b + 15;
! QUERY PLAN
! -----------------------------------------------------------------------------------------------------
Update on public.parent_tbl
Foreign Update on public.foreign_tbl
! Remote SQL: UPDATE public.child_tbl SET b = $3 WHERE ctid = $1 AND tableoid = $2 RETURNING a, b
-> Foreign Scan on public.foreign_tbl
! Output: foreign_tbl.a, (foreign_tbl.b + 15), foreign_tbl.ctid, $0
! Remote SQL: SELECT a, b, ctid, tableoid FROM public.child_tbl WHERE ((a < b)) FOR UPDATE
(6 rows)
UPDATE rw_view SET b = b + 15; -- ok
***************
*** 6808,6820 **** BEFORE UPDATE ON rem1
FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
EXPLAIN (verbose, costs off)
UPDATE rem1 set f2 = ''; -- can't be pushed down
! QUERY PLAN
! ---------------------------------------------------------------------
Update on public.rem1
! Remote SQL: UPDATE public.loc1 SET f2 = $2 WHERE ctid = $1
-> Foreign Scan on public.rem1
! Output: f1, ''::text, ctid, rem1.*
! Remote SQL: SELECT f1, f2, ctid FROM public.loc1 FOR UPDATE
(5 rows)
EXPLAIN (verbose, costs off)
--- 6889,6901 ----
FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
EXPLAIN (verbose, costs off)
UPDATE rem1 set f2 = ''; -- can't be pushed down
! QUERY PLAN
! --------------------------------------------------------------------------------
Update on public.rem1
! Remote SQL: UPDATE public.loc1 SET f2 = $3 WHERE ctid = $1 AND tableoid = $2
-> Foreign Scan on public.rem1
! Output: f1, ''::text, ctid, $0, rem1.*
! Remote SQL: SELECT f1, f2, ctid, tableoid FROM public.loc1 FOR UPDATE
(5 rows)
EXPLAIN (verbose, costs off)
***************
*** 6832,6844 **** AFTER UPDATE ON rem1
FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
EXPLAIN (verbose, costs off)
UPDATE rem1 set f2 = ''; -- can't be pushed down
! QUERY PLAN
! -------------------------------------------------------------------------------
Update on public.rem1
! Remote SQL: UPDATE public.loc1 SET f2 = $2 WHERE ctid = $1 RETURNING f1, f2
-> Foreign Scan on public.rem1
! Output: f1, ''::text, ctid, rem1.*
! Remote SQL: SELECT f1, f2, ctid FROM public.loc1 FOR UPDATE
(5 rows)
EXPLAIN (verbose, costs off)
--- 6913,6925 ----
FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
EXPLAIN (verbose, costs off)
UPDATE rem1 set f2 = ''; -- can't be pushed down
! QUERY PLAN
! -------------------------------------------------------------------------------------------------
Update on public.rem1
! Remote SQL: UPDATE public.loc1 SET f2 = $3 WHERE ctid = $1 AND tableoid = $2 RETURNING f1, f2
-> Foreign Scan on public.rem1
! Output: f1, ''::text, ctid, $0, rem1.*
! Remote SQL: SELECT f1, f2, ctid, tableoid FROM public.loc1 FOR UPDATE
(5 rows)
EXPLAIN (verbose, costs off)
***************
*** 6866,6878 **** UPDATE rem1 set f2 = ''; -- can be pushed down
EXPLAIN (verbose, costs off)
DELETE FROM rem1; -- can't be pushed down
! QUERY PLAN
! ---------------------------------------------------------------------
Delete on public.rem1
! Remote SQL: DELETE FROM public.loc1 WHERE ctid = $1
-> Foreign Scan on public.rem1
! Output: ctid, rem1.*
! Remote SQL: SELECT f1, f2, ctid FROM public.loc1 FOR UPDATE
(5 rows)
DROP TRIGGER trig_row_before_delete ON rem1;
--- 6947,6959 ----
EXPLAIN (verbose, costs off)
DELETE FROM rem1; -- can't be pushed down
! QUERY PLAN
! -------------------------------------------------------------------------------
Delete on public.rem1
! Remote SQL: DELETE FROM public.loc1 WHERE ctid = $1 AND tableoid = $2
-> Foreign Scan on public.rem1
! Output: ctid, $0, rem1.*
! Remote SQL: SELECT f1, f2, ctid, tableoid FROM public.loc1 FOR UPDATE
(5 rows)
DROP TRIGGER trig_row_before_delete ON rem1;
***************
*** 6890,6902 **** UPDATE rem1 set f2 = ''; -- can be pushed down
EXPLAIN (verbose, costs off)
DELETE FROM rem1; -- can't be pushed down
! QUERY PLAN
! ------------------------------------------------------------------------
Delete on public.rem1
! Remote SQL: DELETE FROM public.loc1 WHERE ctid = $1 RETURNING f1, f2
-> Foreign Scan on public.rem1
! Output: ctid, rem1.*
! Remote SQL: SELECT f1, f2, ctid FROM public.loc1 FOR UPDATE
(5 rows)
DROP TRIGGER trig_row_after_delete ON rem1;
--- 6971,6983 ----
EXPLAIN (verbose, costs off)
DELETE FROM rem1; -- can't be pushed down
! QUERY PLAN
! ------------------------------------------------------------------------------------------
Delete on public.rem1
! Remote SQL: DELETE FROM public.loc1 WHERE ctid = $1 AND tableoid = $2 RETURNING f1, f2
-> Foreign Scan on public.rem1
! Output: ctid, $0, rem1.*
! Remote SQL: SELECT f1, f2, ctid, tableoid FROM public.loc1 FOR UPDATE
(5 rows)
DROP TRIGGER trig_row_after_delete ON rem1;
***************
*** 7147,7158 **** select * from bar where f1 in (select f1 from foo) for share;
-- Check UPDATE with inherited target and an inherited source table
explain (verbose, costs off)
update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
! QUERY PLAN
! ---------------------------------------------------------------------------------------------
Update on public.bar
Update on public.bar
Foreign Update on public.bar2
! Remote SQL: UPDATE public.loct2 SET f2 = $2 WHERE ctid = $1
-> Hash Join
Output: bar.f1, (bar.f2 + 100), bar.ctid, foo.ctid, foo.*, foo.tableoid
Inner Unique: true
--- 7228,7239 ----
-- Check UPDATE with inherited target and an inherited source table
explain (verbose, costs off)
update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
! QUERY PLAN
! ---------------------------------------------------------------------------------------------------
Update on public.bar
Update on public.bar
Foreign Update on public.bar2
! Remote SQL: UPDATE public.loct2 SET f2 = $3 WHERE ctid = $1 AND tableoid = $2
-> Hash Join
Output: bar.f1, (bar.f2 + 100), bar.ctid, foo.ctid, foo.*, foo.tableoid
Inner Unique: true
***************
*** 7171,7182 **** update bar set f2 = f2 + 100 where f1 in (select f1 from foo);
Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1
Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
-> Hash Join
! Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, foo.ctid, foo.*, foo.tableoid
Inner Unique: true
Hash Cond: (bar2.f1 = foo.f1)
-> Foreign Scan on public.bar2
! Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid
! Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
-> Hash
Output: foo.ctid, foo.*, foo.tableoid, foo.f1
-> HashAggregate
--- 7252,7263 ----
Output: foo2.ctid, foo2.*, foo2.tableoid, foo2.f1
Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct1
-> Hash Join
! Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, ($0), foo.ctid, foo.*, foo.tableoid
Inner Unique: true
Hash Cond: (bar2.f1 = foo.f1)
-> Foreign Scan on public.bar2
! Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid, $0
! Remote SQL: SELECT f1, f2, f3, ctid, tableoid FROM public.loct2 FOR UPDATE
-> Hash
Output: foo.ctid, foo.*, foo.tableoid, foo.f1
-> HashAggregate
***************
*** 7208,7219 **** update bar set f2 = f2 + 100
from
( select f1 from foo union all select f1+3 from foo ) ss
where bar.f1 = ss.f1;
! QUERY PLAN
! --------------------------------------------------------------------------------------
Update on public.bar
Update on public.bar
Foreign Update on public.bar2
! Remote SQL: UPDATE public.loct2 SET f2 = $2 WHERE ctid = $1
-> Hash Join
Output: bar.f1, (bar.f2 + 100), bar.ctid, (ROW(foo.f1))
Hash Cond: (foo.f1 = bar.f1)
--- 7289,7300 ----
from
( select f1 from foo union all select f1+3 from foo ) ss
where bar.f1 = ss.f1;
! QUERY PLAN
! ------------------------------------------------------------------------------------------------
Update on public.bar
Update on public.bar
Foreign Update on public.bar2
! Remote SQL: UPDATE public.loct2 SET f2 = $3 WHERE ctid = $1 AND tableoid = $2
-> Hash Join
Output: bar.f1, (bar.f2 + 100), bar.ctid, (ROW(foo.f1))
Hash Cond: (foo.f1 = bar.f1)
***************
*** 7233,7246 **** where bar.f1 = ss.f1;
-> Seq Scan on public.bar
Output: bar.f1, bar.f2, bar.ctid
-> Merge Join
! Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, (ROW(foo.f1))
Merge Cond: (bar2.f1 = foo.f1)
-> Sort
! Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid
Sort Key: bar2.f1
-> Foreign Scan on public.bar2
! Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid
! Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
-> Sort
Output: (ROW(foo.f1)), foo.f1
Sort Key: foo.f1
--- 7314,7327 ----
-> Seq Scan on public.bar
Output: bar.f1, bar.f2, bar.ctid
-> Merge Join
! Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, ($0), (ROW(foo.f1))
Merge Cond: (bar2.f1 = foo.f1)
-> Sort
! Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid, ($0)
Sort Key: bar2.f1
-> Foreign Scan on public.bar2
! Output: bar2.f1, bar2.f2, bar2.f3, bar2.ctid, $0
! Remote SQL: SELECT f1, f2, f3, ctid, tableoid FROM public.loct2 FOR UPDATE
-> Sort
Output: (ROW(foo.f1)), foo.f1
Sort Key: foo.f1
***************
*** 7438,7454 **** AFTER UPDATE OR DELETE ON bar2
FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
explain (verbose, costs off)
update bar set f2 = f2 + 100;
! QUERY PLAN
! --------------------------------------------------------------------------------------
Update on public.bar
Update on public.bar
Foreign Update on public.bar2
! Remote SQL: UPDATE public.loct2 SET f2 = $2 WHERE ctid = $1 RETURNING f1, f2, f3
-> Seq Scan on public.bar
Output: bar.f1, (bar.f2 + 100), bar.ctid
-> Foreign Scan on public.bar2
! Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, bar2.*
! Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 FOR UPDATE
(9 rows)
update bar set f2 = f2 + 100;
--- 7519,7535 ----
FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
explain (verbose, costs off)
update bar set f2 = f2 + 100;
! QUERY PLAN
! --------------------------------------------------------------------------------------------------------
Update on public.bar
Update on public.bar
Foreign Update on public.bar2
! Remote SQL: UPDATE public.loct2 SET f2 = $3 WHERE ctid = $1 AND tableoid = $2 RETURNING f1, f2, f3
-> Seq Scan on public.bar
Output: bar.f1, (bar.f2 + 100), bar.ctid
-> Foreign Scan on public.bar2
! Output: bar2.f1, (bar2.f2 + 100), bar2.f3, bar2.ctid, $0, bar2.*
! Remote SQL: SELECT f1, f2, f3, ctid, tableoid FROM public.loct2 FOR UPDATE
(9 rows)
update bar set f2 = f2 + 100;
***************
*** 7466,7483 **** NOTICE: trig_row_after(23, skidoo) AFTER ROW UPDATE ON bar2
NOTICE: OLD: (7,277,77),NEW: (7,377,77)
explain (verbose, costs off)
delete from bar where f2 < 400;
! QUERY PLAN
! ---------------------------------------------------------------------------------------------
Delete on public.bar
Delete on public.bar
Foreign Delete on public.bar2
! Remote SQL: DELETE FROM public.loct2 WHERE ctid = $1 RETURNING f1, f2, f3
-> Seq Scan on public.bar
Output: bar.ctid
Filter: (bar.f2 < 400)
-> Foreign Scan on public.bar2
! Output: bar2.ctid, bar2.*
! Remote SQL: SELECT f1, f2, f3, ctid FROM public.loct2 WHERE ((f2 < 400)) FOR UPDATE
(10 rows)
delete from bar where f2 < 400;
--- 7547,7564 ----
NOTICE: OLD: (7,277,77),NEW: (7,377,77)
explain (verbose, costs off)
delete from bar where f2 < 400;
! QUERY PLAN
! -------------------------------------------------------------------------------------------------------
Delete on public.bar
Delete on public.bar
Foreign Delete on public.bar2
! Remote SQL: DELETE FROM public.loct2 WHERE ctid = $1 AND tableoid = $2 RETURNING f1, f2, f3
-> Seq Scan on public.bar
Output: bar.ctid
Filter: (bar.f2 < 400)
-> Foreign Scan on public.bar2
! Output: bar2.ctid, $0, bar2.*
! Remote SQL: SELECT f1, f2, f3, ctid, tableoid FROM public.loct2 WHERE ((f2 < 400)) FOR UPDATE
(10 rows)
delete from bar where f2 < 400;
*** a/contrib/postgres_fdw/postgres_fdw.c
--- b/contrib/postgres_fdw/postgres_fdw.c
***************
*** 30,35 ****
--- 30,36 ----
#include "optimizer/pathnode.h"
#include "optimizer/paths.h"
#include "optimizer/planmain.h"
+ #include "optimizer/prep.h"
#include "optimizer/restrictinfo.h"
#include "optimizer/var.h"
#include "optimizer/tlist.h"
***************
*** 67,72 **** enum FdwScanPrivateIndex
--- 68,75 ----
FdwScanPrivateSelectSql,
/* Integer list of attribute numbers retrieved by the SELECT */
FdwScanPrivateRetrievedAttrs,
+ /* Param ID for remote table OID for target rel (-1 if none) */
+ FdwScanPrivateTableOidParamId,
/* Integer representing the desired fetch_size */
FdwScanPrivateFetchSize,
***************
*** 133,138 **** typedef struct PgFdwScanState
--- 136,142 ----
/* extracted fdw_private data */
char *query; /* text of SELECT command */
List *retrieved_attrs; /* list of retrieved attribute numbers */
+ int tableoid_param_id; /* Param ID for remote table OID */
/* for remote query execution */
PGconn *conn; /* connection for the scan */
***************
*** 147,152 **** typedef struct PgFdwScanState
--- 151,157 ----
HeapTuple *tuples; /* array of currently-retrieved tuples */
int num_tuples; /* # of tuples in array */
int next_tuple; /* index of next one to return */
+ bool set_tableoid_param; /* Do we need to set the Param? */
/* batch-level state, for optimizing rewinds and avoiding useless fetch */
int fetch_ct_2; /* Min(# of fetches done, 2) */
***************
*** 179,184 **** typedef struct PgFdwModifyState
--- 184,190 ----
/* info about parameters for prepared statement */
AttrNumber ctidAttno; /* attnum of input resjunk ctid column */
+ AttrNumber tableoidAttno; /* attnum of input resjunk tableoid column */
int p_nums; /* number of parameters to transmit */
FmgrInfo *p_flinfo; /* output conversion functions for them */
***************
*** 393,398 **** static PgFdwModifyState *create_foreign_modify(EState *estate,
--- 399,405 ----
static void prepare_foreign_modify(PgFdwModifyState *fmstate);
static const char **convert_prep_stmt_params(PgFdwModifyState *fmstate,
ItemPointer tupleid,
+ Oid tableoid,
TupleTableSlot *slot);
static void store_returning_result(PgFdwModifyState *fmstate,
TupleTableSlot *slot, PGresult *res);
***************
*** 597,602 **** postgresGetForeignRelSize(PlannerInfo *root,
--- 604,632 ----
}
/*
+ * If the table is an UPDATE/DELETE target, the table's reltarget would
+ * have contained a Param representing the remote table OID of the target;
+ * get the Param and save a copy of it in fpinfo for use later.
+ */
+ if (baserel->relid == root->parse->resultRelation)
+ {
+ foreach(lc, baserel->reltarget->exprs)
+ {
+ Param *param = (Param *) lfirst(lc);
+
+ if (IsA(param, Param))
+ {
+ Assert(IS_FOREIGN_PARAM(root, param));
+ fpinfo->tableoid_param = (Param *) copyObject(param);
+ break;
+ }
+ }
+ Assert(fpinfo->tableoid_param);
+ }
+ else
+ fpinfo->tableoid_param = NULL;
+
+ /*
* Compute the selectivity and cost of the local_conds, so we don't have
* to do it over again for each path. The best we can do for these
* conditions is to estimate selectivity on the basis of local statistics.
***************
*** 1139,1144 **** postgresGetForeignPlan(PlannerInfo *root,
--- 1169,1175 ----
List *fdw_scan_tlist = NIL;
List *fdw_recheck_quals = NIL;
List *retrieved_attrs;
+ int tableoid_param_id;
StringInfoData sql;
ListCell *lc;
***************
*** 1278,1289 **** postgresGetForeignPlan(PlannerInfo *root,
/* Remember remote_exprs for possible use by postgresPlanDirectModify */
fpinfo->final_remote_exprs = remote_exprs;
/*
* Build the fdw_private list that will be available to the executor.
* Items in the list must match order in enum FdwScanPrivateIndex.
*/
! fdw_private = list_make3(makeString(sql.data),
retrieved_attrs,
makeInteger(fpinfo->fetch_size));
if (IS_JOIN_REL(foreignrel) || IS_UPPER_REL(foreignrel))
fdw_private = lappend(fdw_private,
--- 1309,1327 ----
/* Remember remote_exprs for possible use by postgresPlanDirectModify */
fpinfo->final_remote_exprs = remote_exprs;
+ /* Get the Param ID for the remote table OID, if it exists */
+ if (fpinfo->tableoid_param)
+ tableoid_param_id = fpinfo->tableoid_param->paramid;
+ else
+ tableoid_param_id = -1;
+
/*
* Build the fdw_private list that will be available to the executor.
* Items in the list must match order in enum FdwScanPrivateIndex.
*/
! fdw_private = list_make4(makeString(sql.data),
retrieved_attrs,
+ makeInteger(tableoid_param_id),
makeInteger(fpinfo->fetch_size));
if (IS_JOIN_REL(foreignrel) || IS_UPPER_REL(foreignrel))
fdw_private = lappend(fdw_private,
***************
*** 1367,1372 **** postgresBeginForeignScan(ForeignScanState *node, int eflags)
--- 1405,1412 ----
FdwScanPrivateSelectSql));
fsstate->retrieved_attrs = (List *) list_nth(fsplan->fdw_private,
FdwScanPrivateRetrievedAttrs);
+ fsstate->tableoid_param_id = intVal(list_nth(fsplan->fdw_private,
+ FdwScanPrivateTableOidParamId));
fsstate->fetch_size = intVal(list_nth(fsplan->fdw_private,
FdwScanPrivateFetchSize));
***************
*** 1381,1396 **** postgresBeginForeignScan(ForeignScanState *node, int eflags)
--- 1421,1447 ----
/*
* Get info we'll need for converting data fetched from the foreign server
* into local representation and error reporting during that process.
+ * Also, determine whether we need to set the Param for the remote table
+ * OID.
*/
if (fsplan->scan.scanrelid > 0)
{
fsstate->rel = node->ss.ss_currentRelation;
fsstate->tupdesc = RelationGetDescr(fsstate->rel);
+
+ fsstate->set_tableoid_param =
+ fsstate->tableoid_param_id >= 0 ? true : false;
}
else
{
fsstate->rel = NULL;
fsstate->tupdesc = node->ss.ss_ScanTupleSlot->tts_tupleDescriptor;
+
+ /*
+ * No need to set the Param since the value will be produced as a
+ * tlist entry of fdw_scan_tlist, even if it exists.
+ */
+ fsstate->set_tableoid_param = false;
}
fsstate->attinmeta = TupleDescGetAttInMetadata(fsstate->tupdesc);
***************
*** 1419,1424 **** postgresIterateForeignScan(ForeignScanState *node)
--- 1470,1476 ----
{
PgFdwScanState *fsstate = (PgFdwScanState *) node->fdw_state;
TupleTableSlot *slot = node->ss.ss_ScanTupleSlot;
+ HeapTuple tuple;
/*
* If this is the first call after Begin or ReScan, we need to create the
***************
*** 1441,1451 **** postgresIterateForeignScan(ForeignScanState *node)
}
/*
* Return the next tuple.
*/
! ExecStoreHeapTuple(fsstate->tuples[fsstate->next_tuple++],
! slot,
! false);
return slot;
}
--- 1493,1520 ----
}
/*
+ * Get the next tuple.
+ */
+ tuple = fsstate->tuples[fsstate->next_tuple++];
+
+ /*
+ * Set the Param for the remote table OID, if necessary.
+ */
+ if (fsstate->set_tableoid_param)
+ {
+ ExprContext *econtext = node->ss.ps.ps_ExprContext;
+ ParamExecData *prm = &(econtext->ecxt_param_exec_vals[fsstate->tableoid_param_id]);
+
+ Assert(OidIsValid(tuple->t_tableOid));
+ prm->execPlan = NULL;
+ prm->value = ObjectIdGetDatum(tuple->t_tableOid);
+ prm->isnull = false;
+ }
+
+ /*
* Return the next tuple.
*/
! ExecStoreHeapTuple(tuple, slot, false);
return slot;
}
***************
*** 1540,1553 **** postgresAddForeignUpdateTargets(Query *parsetree,
Relation target_relation)
{
Var *var;
const char *attrname;
TargetEntry *tle;
/*
! * In postgres_fdw, what we need is the ctid, same as for a regular table.
*/
! /* Make a Var representing the desired value */
var = makeVar(parsetree->resultRelation,
SelfItemPointerAttributeNumber,
TIDOID,
--- 1609,1625 ----
Relation target_relation)
{
Var *var;
+ Param *param;
const char *attrname;
TargetEntry *tle;
/*
! * In postgres_fdw, what we need is the ctid, same as for a regular table,
! * and the remote table OID, which is needed since the remote table might
! * be an inheritance tree.
*/
! /* Make a Var representing the ctid value */
var = makeVar(parsetree->resultRelation,
SelfItemPointerAttributeNumber,
TIDOID,
***************
*** 1565,1570 **** postgresAddForeignUpdateTargets(Query *parsetree,
--- 1637,1656 ----
/* ... and add it to the query's targetlist */
parsetree->targetList = lappend(parsetree->targetList, tle);
+
+ /* Make a Param representing the tableoid value */
+ param = generate_foreign_param(OIDOID, -1, InvalidOid);
+
+ /* Wrap it in a resjunk TLE with the right name ... */
+ attrname = "remotetableoid";
+
+ tle = makeTargetEntry((Expr *) param,
+ list_length(parsetree->targetList) + 1,
+ pstrdup(attrname),
+ true);
+
+ /* ... and add it to the query's targetlist */
+ parsetree->targetList = lappend(parsetree->targetList, tle);
}
/*
***************
*** 1768,1774 **** postgresExecForeignInsert(EState *estate,
prepare_foreign_modify(fmstate);
/* Convert parameters needed by prepared statement to text form */
! p_values = convert_prep_stmt_params(fmstate, NULL, slot);
/*
* Execute the prepared statement.
--- 1854,1860 ----
prepare_foreign_modify(fmstate);
/* Convert parameters needed by prepared statement to text form */
! p_values = convert_prep_stmt_params(fmstate, NULL, InvalidOid, slot);
/*
* Execute the prepared statement.
***************
*** 1824,1829 **** postgresExecForeignUpdate(EState *estate,
--- 1910,1916 ----
{
PgFdwModifyState *fmstate = (PgFdwModifyState *) resultRelInfo->ri_FdwState;
Datum datum;
+ Datum datum2;
bool isNull;
const char **p_values;
PGresult *res;
***************
*** 1841,1849 **** postgresExecForeignUpdate(EState *estate,
--- 1928,1945 ----
if (isNull)
elog(ERROR, "ctid is NULL");
+ /* Get the tableoid that was passed up as a resjunk column */
+ datum2 = ExecGetJunkAttribute(planSlot,
+ fmstate->tableoidAttno,
+ &isNull);
+ /* shouldn't ever get a null result... */
+ if (isNull)
+ elog(ERROR, "tableoid is NULL");
+
/* Convert parameters needed by prepared statement to text form */
p_values = convert_prep_stmt_params(fmstate,
(ItemPointer) DatumGetPointer(datum),
+ DatumGetObjectId(datum2),
slot);
/*
***************
*** 1900,1905 **** postgresExecForeignDelete(EState *estate,
--- 1996,2002 ----
{
PgFdwModifyState *fmstate = (PgFdwModifyState *) resultRelInfo->ri_FdwState;
Datum datum;
+ Datum datum2;
bool isNull;
const char **p_values;
PGresult *res;
***************
*** 1917,1925 **** postgresExecForeignDelete(EState *estate,
--- 2014,2031 ----
if (isNull)
elog(ERROR, "ctid is NULL");
+ /* Get the tableoid that was passed up as a resjunk column */
+ datum2 = ExecGetJunkAttribute(planSlot,
+ fmstate->tableoidAttno,
+ &isNull);
+ /* shouldn't ever get a null result... */
+ if (isNull)
+ elog(ERROR, "tableoid is NULL");
+
/* Convert parameters needed by prepared statement to text form */
p_values = convert_prep_stmt_params(fmstate,
(ItemPointer) DatumGetPointer(datum),
+ DatumGetObjectId(datum2),
NULL);
/*
***************
*** 3340,3346 **** create_foreign_modify(EState *estate,
fmstate->attinmeta = TupleDescGetAttInMetadata(tupdesc);
/* Prepare for output conversion of parameters used in prepared stmt. */
! n_params = list_length(fmstate->target_attrs) + 1;
fmstate->p_flinfo = (FmgrInfo *) palloc0(sizeof(FmgrInfo) * n_params);
fmstate->p_nums = 0;
--- 3446,3452 ----
fmstate->attinmeta = TupleDescGetAttInMetadata(tupdesc);
/* Prepare for output conversion of parameters used in prepared stmt. */
! n_params = list_length(fmstate->target_attrs) + 2;
fmstate->p_flinfo = (FmgrInfo *) palloc0(sizeof(FmgrInfo) * n_params);
fmstate->p_nums = 0;
***************
*** 3358,3363 **** create_foreign_modify(EState *estate,
--- 3464,3480 ----
getTypeOutputInfo(TIDOID, &typefnoid, &isvarlena);
fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]);
fmstate->p_nums++;
+
+ /* Find the tableoid resjunk column in the subplan's result */
+ fmstate->tableoidAttno = ExecFindJunkAttributeInTlist(subplan->targetlist,
+ "remotetableoid");
+ if (!AttributeNumberIsValid(fmstate->tableoidAttno))
+ elog(ERROR, "could not find junk tableoid column");
+
+ /* Second transmittable parameter will be tableoid */
+ getTypeOutputInfo(OIDOID, &typefnoid, &isvarlena);
+ fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]);
+ fmstate->p_nums++;
}
if (operation == CMD_INSERT || operation == CMD_UPDATE)
***************
*** 3431,3436 **** prepare_foreign_modify(PgFdwModifyState *fmstate)
--- 3548,3554 ----
* Create array of text strings representing parameter values
*
* tupleid is ctid to send, or NULL if none
+ * tableoid is tableoid to send, or InvalidOid if none
* slot is slot to get remaining parameters from, or NULL if none
*
* Data is constructed in temp_cxt; caller should reset that after use.
***************
*** 3438,3443 **** prepare_foreign_modify(PgFdwModifyState *fmstate)
--- 3556,3562 ----
static const char **
convert_prep_stmt_params(PgFdwModifyState *fmstate,
ItemPointer tupleid,
+ Oid tableoid,
TupleTableSlot *slot)
{
const char **p_values;
***************
*** 3457,3462 **** convert_prep_stmt_params(PgFdwModifyState *fmstate,
--- 3576,3592 ----
pindex++;
}
+ /* 2nd parameter should be tableoid, if it's in use */
+ if (OidIsValid(tableoid))
+ {
+ Assert(tupleid != NULL);
+
+ /* don't need set_transmission_modes for OID output */
+ p_values[pindex] = OutputFunctionCall(&fmstate->p_flinfo[pindex],
+ ObjectIdGetDatum(tableoid));
+ pindex++;
+ }
+
/* get following parameters from slot */
if (slot != NULL && fmstate->target_attrs != NIL)
{
***************
*** 3846,3851 **** init_returning_filter(PgFdwDirectModifyState *dmstate,
--- 3976,3992 ----
TargetEntry *tle = (TargetEntry *) lfirst(lc);
Var *var = (Var *) tle->expr;
+ /*
+ * No need to set the Param for the remote table OID; ignore it.
+ */
+ if (IsA(var, Param))
+ {
+ /* We would not retrieve the remote table OID anymore. */
+ Assert(!list_member_int(dmstate->retrieved_attrs, i));
+ i++;
+ continue;
+ }
+
Assert(IsA(var, Var));
/*
***************
*** 4883,4888 **** foreign_join_ok(PlannerInfo *root, RelOptInfo *joinrel, JoinType jointype,
--- 5024,5062 ----
/* Mark that this join can be pushed down safely */
fpinfo->pushdown_safe = true;
+ /*
+ * If the join relation contains an UPDATE/DELETE target, either of the
+ * input relations would have saved the Param representing the remote
+ * table OID of the target; get the Param and remember it in fpinfo for
+ * use later.
+ */
+ if ((root->parse->commandType == CMD_UPDATE ||
+ root->parse->commandType == CMD_DELETE) &&
+ bms_is_member(root->parse->resultRelation, joinrel->relids))
+ {
+ if (bms_is_member(root->parse->resultRelation,
+ outerrel->relids))
+ {
+ Assert(fpinfo_o->tableoid_param);
+ fpinfo->tableoid_param = fpinfo_o->tableoid_param;
+ }
+ else
+ {
+ Assert(bms_is_member(root->parse->resultRelation,
+ innerrel->relids));
+ Assert(fpinfo_i->tableoid_param);
+ fpinfo->tableoid_param = fpinfo_i->tableoid_param;
+ }
+
+ /*
+ * Core code should have contained the Param in the join relation's
+ * reltarget.
+ */
+ Assert(list_member(joinrel->reltarget->exprs, fpinfo->tableoid_param));
+ }
+ else
+ fpinfo->tableoid_param = NULL;
+
/* Get user mapping */
if (fpinfo->use_remote_estimate)
{
***************
*** 5556,5561 **** make_tuple_from_result_row(PGresult *res,
--- 5730,5736 ----
bool *nulls;
ItemPointer ctid = NULL;
Oid oid = InvalidOid;
+ Oid tableoid = InvalidOid;
ConversionLocation errpos;
ErrorContextCallback errcallback;
MemoryContext oldcontext;
***************
*** 5649,5654 **** make_tuple_from_result_row(PGresult *res,
--- 5824,5840 ----
oid = DatumGetObjectId(datum);
}
}
+ else if (i == TableOidAttributeNumber)
+ {
+ /* tableoid */
+ if (valstr != NULL)
+ {
+ Datum datum;
+
+ datum = DirectFunctionCall1(oidin, CStringGetDatum(valstr));
+ tableoid = DatumGetObjectId(datum);
+ }
+ }
errpos.cur_attno = 0;
j++;
***************
*** 5698,5703 **** make_tuple_from_result_row(PGresult *res,
--- 5884,5899 ----
if (OidIsValid(oid))
HeapTupleSetOid(tuple, oid);
+ /*
+ * If we have a table OID to return, install it. (Note that this is not
+ * really right because the installed value is the value on the remote
+ * side, not the local side, but we do this for use by
+ * postgresIterateForeignScan(). The correct value will be re-installed
+ * in ForeignNext() if necessary.)
+ */
+ if (OidIsValid(tableoid))
+ tuple->t_tableOid = tableoid;
+
/* Clean up */
MemoryContextReset(temp_context);
***************
*** 5714,5719 **** conversion_error_callback(void *arg)
--- 5910,5916 ----
const char *attname = NULL;
const char *relname = NULL;
bool is_wholerow = false;
+ bool is_tableoid = false;
ConversionLocation *errpos = (ConversionLocation *) arg;
if (errpos->rel)
***************
*** 5728,5733 **** conversion_error_callback(void *arg)
--- 5925,5932 ----
attname = "ctid";
else if (errpos->cur_attno == ObjectIdAttributeNumber)
attname = "oid";
+ else if (errpos->cur_attno == TableOidAttributeNumber)
+ is_tableoid = true;
relname = RelationGetRelationName(errpos->rel);
}
***************
*** 5744,5751 **** conversion_error_callback(void *arg)
/*
* Target list can have Vars and expressions. For Vars, we can get
! * its relation, however for expressions we can't. Thus for
! * expressions, just show generic context message.
*/
if (IsA(tle->expr, Var))
{
--- 5943,5952 ----
/*
* Target list can have Vars and expressions. For Vars, we can get
! * its relation, however for expressions we can't, except for the
! * Param representing the remote table OID, in which case we can.
! * Thus for expressions other than the Param, just show generic
! * context message.
*/
if (IsA(tle->expr, Var))
{
***************
*** 5761,5766 **** conversion_error_callback(void *arg)
--- 5962,5981 ----
relname = get_rel_name(rte->relid);
}
+ else if (IsA(tle->expr, Param))
+ {
+ RangeTblEntry *rte;
+ ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
+
+ Assert(((Param *) tle->expr)->paramid == ((PgFdwScanState *) fsstate->fdw_state)->tableoid_param_id);
+ Assert(resultRelInfo);
+
+ is_tableoid = true;
+
+ rte = exec_rt_fetch(resultRelInfo->ri_RangeTableIndex, estate);
+
+ relname = get_rel_name(rte->relid);
+ }
else
errcontext("processing expression at position %d in select list",
errpos->cur_attno);
***************
*** 5770,5775 **** conversion_error_callback(void *arg)
--- 5985,5992 ----
{
if (is_wholerow)
errcontext("whole-row reference to foreign table \"%s\"", relname);
+ else if (is_tableoid)
+ errcontext("remote tableoid of foreign table \"%s\"", relname);
else if (attname)
errcontext("column \"%s\" of foreign table \"%s\"", attname, relname);
}
*** a/contrib/postgres_fdw/postgres_fdw.h
--- b/contrib/postgres_fdw/postgres_fdw.h
***************
*** 49,54 **** typedef struct PgFdwRelationInfo
--- 49,57 ----
/* Bitmap of attr numbers we need to fetch from the remote server. */
Bitmapset *attrs_used;
+ /* PARAM_EXEC Param representing the remote table OID of a target rel */
+ Param *tableoid_param;
+
/* Cost and selectivity of local_conds. */
QualCost local_conds_cost;
Selectivity local_conds_sel;
*** a/contrib/postgres_fdw/sql/postgres_fdw.sql
--- b/contrib/postgres_fdw/sql/postgres_fdw.sql
***************
*** 1228,1233 **** SELECT * FROM ft1 ORDER BY c6 DESC NULLS FIRST, c1 OFFSET 15 LIMIT 10;
--- 1228,1266 ----
EXPLAIN (VERBOSE, COSTS OFF) SELECT * FROM ft1 ORDER BY c6 ASC NULLS FIRST, c1 OFFSET 15 LIMIT 10;
SELECT * FROM ft1 ORDER BY c6 ASC NULLS FIRST, c1 OFFSET 15 LIMIT 10;
+ -- Test UPDATE/DELETE in the case where the remote target is
+ -- an inheritance tree
+ CREATE TABLE p1 (a int, b int);
+ CREATE TABLE c1 (LIKE p1) INHERITS (p1);
+ CREATE TABLE c2 (LIKE p1) INHERITS (p1);
+ CREATE FOREIGN TABLE fp1 (a int, b int)
+ SERVER loopback OPTIONS (table_name 'p1');
+ INSERT INTO c1 VALUES (0, 1);
+ INSERT INTO c2 VALUES (1, 1);
+ SELECT tableoid::regclass, ctid, * FROM fp1;
+ -- Update statement should not be pushed down to the remote side
+ EXPLAIN (VERBOSE, COSTS OFF)
+ UPDATE fp1 SET b = b + 1 WHERE a = 0 AND random() <= 1;
+ UPDATE fp1 SET b = b + 1 WHERE a = 0 AND random() <= 1;
+ -- Only one tuple should be updated
+ SELECT tableoid::regclass, ctid, * FROM fp1;
+ -- Recreate the table data
+ TRUNCATE c1;
+ TRUNCATE c2;
+ INSERT INTO c1 VALUES (0, 1);
+ INSERT INTO c2 VALUES (1, 1);
+ SELECT tableoid::regclass, ctid, * FROM fp1;
+ -- Delete statement should not be pushed down to the remote side
+ EXPLAIN (VERBOSE, COSTS OFF)
+ DELETE FROM fp1 WHERE a = 1 AND random() <= 1;
+ DELETE FROM fp1 WHERE a = 1 AND random() <= 1;
+ -- Only one tuple should be deleted
+ SELECT tableoid::regclass, ctid, * FROM fp1;
+
+ -- cleanup
+ DROP FOREIGN TABLE fp1;
+ DROP TABLE p1 CASCADE;
+
-- ===================================================================
-- test check constraints
-- ===================================================================
*** a/doc/src/sgml/fdwhandler.sgml
--- b/doc/src/sgml/fdwhandler.sgml
***************
*** 440,445 **** AddForeignUpdateTargets(Query *parsetree,
--- 440,454 ----
</para>
<para>
+ This function may also add to the targetlist <structname>Param</structname>
+ nodes representing extra target information, in which case
+ <function>IterateForeignScan</function> must set the values of these parameters
+ for each row fetched from the foreign source; it's recommended to use
+ <function>generate_foreign_param</function> to build the
+ <structname>Param</structname> nodes.
+ </para>
+
+ <para>
Although this function is called during planning, the
information provided is a bit different from that available to other
planning routines.
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 2272,2277 **** _outPlannerGlobal(StringInfo str, const PlannerGlobal *node)
--- 2272,2278 ----
WRITE_BOOL_FIELD(parallelModeOK);
WRITE_BOOL_FIELD(parallelModeNeeded);
WRITE_CHAR_FIELD(maxParallelHazard);
+ WRITE_BITMAPSET_FIELD(foreignParamIDs);
}
static void
*** a/src/backend/optimizer/plan/createplan.c
--- b/src/backend/optimizer/plan/createplan.c
***************
*** 892,897 **** use_physical_tlist(PlannerInfo *root, Path *path, int flags)
--- 892,917 ----
}
}
+ /*
+ * Also, can't do it to a ForeignPath if the path is requested to emit
+ * Params generated by the FDW.
+ */
+ if (IsA(path, ForeignPath) &&
+ path->parent->relid == root->parse->resultRelation &&
+ !bms_is_empty(root->glob->foreignParamIDs))
+ {
+ foreach(lc, path->pathtarget->exprs)
+ {
+ Param *param = (Param *) lfirst(lc);
+
+ if (param && IsA(param, Param))
+ {
+ Assert(IS_FOREIGN_PARAM(root, param));
+ return false;
+ }
+ }
+ }
+
return true;
}
*** a/src/backend/optimizer/plan/initsplan.c
--- b/src/backend/optimizer/plan/initsplan.c
***************
*** 29,34 ****
--- 29,35 ----
#include "optimizer/restrictinfo.h"
#include "optimizer/var.h"
#include "parser/analyze.h"
+ #include "parser/parsetree.h"
#include "rewrite/rewriteManip.h"
#include "utils/lsyscache.h"
***************
*** 46,51 **** typedef struct PostponedQual
--- 47,55 ----
} PostponedQual;
+ static void add_params_to_result_rel(PlannerInfo *root,
+ int result_relation,
+ List *final_tlist);
static void extract_lateral_references(PlannerInfo *root, RelOptInfo *brel,
Index rtindex);
static List *deconstruct_recurse(PlannerInfo *root, Node *jtnode,
***************
*** 146,151 **** add_base_rels_to_query(PlannerInfo *root, Node *jtnode)
--- 150,161 ----
*
* We mark such vars as needed by "relation 0" to ensure that they will
* propagate up through all join plan steps.
+ *
+ * If this is an UPDATE/DELETE on a foreign table, the FDW might have added
+ * Params to the final tlist that are needed for identifying the rows to be
+ * updated or deleted. Add targetlist entries for each such Param to the
+ * result relation. Note that it's ensured by build_joinrel_tlist() that
+ * such Params will also propagate up through all join plan steps.
*/
void
build_base_rel_tlists(PlannerInfo *root, List *final_tlist)
***************
*** 178,183 **** build_base_rel_tlists(PlannerInfo *root, List *final_tlist)
--- 188,206 ----
list_free(having_vars);
}
}
+
+ /*
+ * If this is an UPDATE/DELETE on a foreign table, add targetlist entries
+ * for Params the FDW generated (if any) to the result relation.
+ */
+ if (root->parse->commandType == CMD_UPDATE ||
+ root->parse->commandType == CMD_DELETE)
+ {
+ int result_relation = root->parse->resultRelation;
+
+ if (planner_rt_fetch(result_relation, root)->relkind == RELKIND_FOREIGN_TABLE)
+ add_params_to_result_rel(root, result_relation, final_tlist);
+ }
}
/*
***************
*** 241,246 **** add_vars_to_targetlist(PlannerInfo *root, List *vars,
--- 264,303 ----
}
}
+ /*
+ * add_params_to_result_rel
+ * If the query's final tlist contains Params the FDW generated, add
+ * targetlist entries for each such Param to the result relation.
+ */
+ static void
+ add_params_to_result_rel(PlannerInfo *root, int result_relation,
+ List *final_tlist)
+ {
+ RelOptInfo *target_rel = find_base_rel(root, result_relation);
+ ListCell *lc;
+
+ /*
+ * If no parameters have been generated by any FDWs, we certainly don't
+ * need to do anything here.
+ */
+ if (bms_is_empty(root->glob->foreignParamIDs))
+ return;
+
+ foreach(lc, final_tlist)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(lc);
+ Param *param = (Param *) tle->expr;
+
+ if (tle->resjunk && IsA(param, Param) &&
+ IS_FOREIGN_PARAM(root, param))
+ {
+ /* XXX is copyObject necessary here? */
+ target_rel->reltarget->exprs = lappend(target_rel->reltarget->exprs,
+ copyObject(param));
+ }
+ }
+ }
+
/*****************************************************************************
*
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
***************
*** 313,318 **** standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
--- 313,319 ----
glob->lastPlanNodeId = 0;
glob->transientPlan = false;
glob->dependsOnRole = false;
+ glob->foreignParamIDs = NULL;
/*
* Assess whether it's feasible to use parallel mode for this query. We
***************
*** 481,492 **** standard_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
}
/*
! * If any Params were generated, run through the plan tree and compute
! * each plan node's extParam/allParam sets. Ideally we'd merge this into
! * set_plan_references' tree traversal, but for now it has to be separate
! * because we need to visit subplans before not after main plan.
*/
! if (glob->paramExecTypes != NIL)
{
Assert(list_length(glob->subplans) == list_length(glob->subroots));
forboth(lp, glob->subplans, lr, glob->subroots)
--- 482,497 ----
}
/*
! * If any Params were generated by the planner not by FDWs, run through
! * the plan tree and compute each plan node's extParam/allParam sets.
! * (Params added by FDWs are irrelevant for parameter change signaling.)
! * Ideally we'd merge this into set_plan_references' tree traversal, but
! * for now it has to be separate because we need to visit subplans before
! * not after main plan.
*/
! if (glob->paramExecTypes != NIL &&
! (bms_is_empty(glob->foreignParamIDs) ||
! bms_num_members(glob->foreignParamIDs) < list_length(glob->paramExecTypes)))
{
Assert(list_length(glob->subplans) == list_length(glob->subroots));
forboth(lp, glob->subplans, lr, glob->subroots)
*** a/src/backend/optimizer/plan/setrefs.c
--- b/src/backend/optimizer/plan/setrefs.c
***************
*** 168,184 **** static bool extract_query_dependencies_walker(Node *node,
* 5. PARAM_MULTIEXPR Params are replaced by regular PARAM_EXEC Params,
* now that we have finished planning all MULTIEXPR subplans.
*
! * 6. We compute regproc OIDs for operators (ie, we look up the function
* that implements each op).
*
! * 7. We create lists of specific objects that the plan depends on.
* This will be used by plancache.c to drive invalidation of cached plans.
* Relation dependencies are represented by OIDs, and everything else by
* PlanInvalItems (this distinction is motivated by the shared-inval APIs).
* Currently, relations and user-defined functions are the only types of
* objects that are explicitly tracked this way.
*
! * 8. We assign every plan node in the tree a unique ID.
*
* We also perform one final optimization step, which is to delete
* SubqueryScan plan nodes that aren't doing anything useful (ie, have
--- 168,187 ----
* 5. PARAM_MULTIEXPR Params are replaced by regular PARAM_EXEC Params,
* now that we have finished planning all MULTIEXPR subplans.
*
! * 6. PARAM_EXEC Params generated by FDWs in upper plan nodes are converted
! * into simple Vars referencing the outputs of their subplans.
! *
! * 7. We compute regproc OIDs for operators (ie, we look up the function
* that implements each op).
*
! * 8. We create lists of specific objects that the plan depends on.
* This will be used by plancache.c to drive invalidation of cached plans.
* Relation dependencies are represented by OIDs, and everything else by
* PlanInvalItems (this distinction is motivated by the shared-inval APIs).
* Currently, relations and user-defined functions are the only types of
* objects that are explicitly tracked this way.
*
! * 9. We assign every plan node in the tree a unique ID.
*
* We also perform one final optimization step, which is to delete
* SubqueryScan plan nodes that aren't doing anything useful (ie, have
***************
*** 2343,2349 **** fix_join_expr_mutator(Node *node, fix_join_expr_context *context)
--- 2346,2389 ----
return fix_join_expr_mutator((Node *) phv->phexpr, context);
}
if (IsA(node, Param))
+ {
+ Param *param = (Param *) node;
+
+ /*
+ * If the Param is a PARAM_EXEC Param generated by an FDW, it should
+ * have bubbled up from a lower plan node; convert it into a simple
+ * Var referencing the output of the subplan.
+ *
+ * Note: set_join_references() would have kept has_non_vars=true for
+ * the subplan emitting the Param since it effectively belong to the
+ * result relation and that relation can never be the nullable side of
+ * an outer join.
+ */
+ if (IS_FOREIGN_PARAM(context->root, param))
+ {
+ if (context->outer_itlist && context->outer_itlist->has_non_vars)
+ {
+ newvar = search_indexed_tlist_for_non_var((Expr *) node,
+ context->outer_itlist,
+ OUTER_VAR);
+ if (newvar)
+ return (Node *) newvar;
+ }
+ if (context->inner_itlist && context->inner_itlist->has_non_vars)
+ {
+ newvar = search_indexed_tlist_for_non_var((Expr *) node,
+ context->inner_itlist,
+ INNER_VAR);
+ if (newvar)
+ return (Node *) newvar;
+ }
+ /* No referent found for foreign Param */
+ elog(ERROR, "foreign parameter not found in subplan target lists");
+ }
+
+ /* If not, do fix_param_node() */
return fix_param_node(context->root, (Param *) node);
+ }
/* Try matching more complex expressions too, if tlists have any */
if (context->outer_itlist && context->outer_itlist->has_non_vars)
{
***************
*** 2449,2455 **** fix_upper_expr_mutator(Node *node, fix_upper_expr_context *context)
--- 2489,2518 ----
return fix_upper_expr_mutator((Node *) phv->phexpr, context);
}
if (IsA(node, Param))
+ {
+ Param *param = (Param *) node;
+
+ /*
+ * If the Param is a PARAM_EXEC Param generated by an FDW, it should
+ * have bubbled up from a lower plan node; convert it into a simple
+ * Var referencing the output of the subplan.
+ */
+ if (IS_FOREIGN_PARAM(context->root, param))
+ {
+ if (context->subplan_itlist->has_non_vars)
+ {
+ newvar = search_indexed_tlist_for_non_var((Expr *) node,
+ context->subplan_itlist,
+ context->newvarno);
+ if (newvar)
+ return (Node *) newvar;
+ }
+ /* No referent found for foreign Param */
+ elog(ERROR, "foreign parameter not found in subplan target list");
+ }
+ /* If not, do fix_param_node() */
return fix_param_node(context->root, (Param *) node);
+ }
if (IsA(node, Aggref))
{
Aggref *aggref = (Aggref *) node;
*** a/src/backend/optimizer/plan/subselect.c
--- b/src/backend/optimizer/plan/subselect.c
***************
*** 2536,2542 **** finalize_plan(PlannerInfo *root, Plan *plan,
finalize_primnode((Node *) fscan->fdw_recheck_quals,
&context);
! /* We assume fdw_scan_tlist cannot contain Params */
context.paramids = bms_add_members(context.paramids,
scan_params);
}
--- 2536,2546 ----
finalize_primnode((Node *) fscan->fdw_recheck_quals,
&context);
! /*
! * We assume fdw_scan_tlist cannot contain Params other than
! * ones generated by the FDW, which are never used for
! * changed-param signaling.
! */
context.paramids = bms_add_members(context.paramids,
scan_params);
}
***************
*** 2897,2903 **** finalize_primnode(Node *node, finalize_primnode_context *context)
{
int paramid = ((Param *) node)->paramid;
! context->paramids = bms_add_member(context->paramids, paramid);
}
return false; /* no more to do here */
}
--- 2901,2912 ----
{
int paramid = ((Param *) node)->paramid;
! /*
! * Params added by FDWs are irrelevant for parameter change
! * signaling.
! */
! if (!bms_is_member(paramid, context->root->glob->foreignParamIDs))
! context->paramids = bms_add_member(context->paramids, paramid);
}
return false; /* no more to do here */
}
*** a/src/backend/optimizer/prep/preptlist.c
--- b/src/backend/optimizer/prep/preptlist.c
***************
*** 55,60 ****
--- 55,61 ----
static List *expand_targetlist(List *tlist, int command_type,
Index result_relation, Relation rel);
+ static void fix_foreign_params(PlannerInfo *root, List *tlist);
/*
***************
*** 106,113 **** preprocess_targetlist(PlannerInfo *root)
--- 107,120 ----
* keep it that way to avoid changing APIs used by FDWs.
*/
if (command_type == CMD_UPDATE || command_type == CMD_DELETE)
+ {
rewriteTargetListUD(parse, target_rte, target_relation);
+ /* The FDW might have added Params; fix such Params, if any */
+ if (target_rte->relkind == RELKIND_FOREIGN_TABLE)
+ fix_foreign_params(root, parse->targetList);
+ }
+
/*
* for heap_form_tuple to work, the targetlist must match the exact order
* of the attributes. We also need to fill in any missing attributes. -ay
***************
*** 416,421 **** expand_targetlist(List *tlist, int command_type,
--- 423,479 ----
/*
+ * Generate a new Param node needed for an UPDATE/DELETE on a foreign table
+ *
+ * This is used by the FDW to build PARAM_EXEC Params representing extra
+ * information to ensure that it can identify the exact row to update or
+ * delete.
+ */
+ Param *
+ generate_foreign_param(Oid paramtype, int32 paramtypmod, Oid paramcollation)
+ {
+ Param *retval;
+
+ retval = makeNode(Param);
+ retval->paramkind = PARAM_EXEC;
+ /* paramid will be filled in by fix_foreign_params */
+ retval->paramid = -1;
+ retval->paramtype = paramtype;
+ retval->paramtypmod = paramtypmod;
+ retval->paramcollid = paramcollation;
+ retval->location = -1;
+
+ return retval;
+ }
+
+ /*
+ * Fix the paramids of PARAM_EXEC Params the FDW added to the tlist, if any.
+ */
+ static void
+ fix_foreign_params(PlannerInfo *root, List *tlist)
+ {
+ ListCell *lc;
+
+ foreach(lc, tlist)
+ {
+ TargetEntry *tle = (TargetEntry *) lfirst(lc);
+ Param *param = (Param *) tle->expr;
+
+ if (tle->resjunk && IsA(param, Param) &&
+ param->paramkind == PARAM_EXEC &&
+ param->paramid == -1)
+ {
+ param->paramid = list_length(root->glob->paramExecTypes);
+ root->glob->paramExecTypes =
+ lappend_oid(root->glob->paramExecTypes, param->paramtype);
+ root->glob->foreignParamIDs =
+ bms_add_member(root->glob->foreignParamIDs, param->paramid);
+ }
+ }
+ }
+
+
+ /*
* Locate PlanRowMark for given RT index, or return NULL if none
*
* This probably ought to be elsewhere, but there's no very good place
*** a/src/backend/optimizer/util/relnode.c
--- b/src/backend/optimizer/util/relnode.c
***************
*** 28,33 ****
--- 28,34 ----
#include "optimizer/tlist.h"
#include "partitioning/partbounds.h"
#include "utils/hsearch.h"
+ #include "utils/lsyscache.h"
typedef struct JoinHashEntry
***************
*** 913,918 **** build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
--- 914,936 ----
RelOptInfo *baserel;
int ndx;
+ /* Params are needed for final output, so add them to the output. */
+ if (IsA(var, Param))
+ {
+ Param *param = (Param *) var;
+
+ Assert(IS_FOREIGN_PARAM(root, param));
+ joinrel->reltarget->exprs =
+ lappend(joinrel->reltarget->exprs, param);
+ /*
+ * Estimate using the type info (Note: keep this in sync with
+ * set_rel_width())
+ */
+ joinrel->reltarget->width +=
+ get_typavgwidth(param->paramtype, param->paramtypmod);
+ continue;
+ }
+
/*
* Ignore PlaceHolderVars in the input tlists; we'll make our own
* decisions about whether to copy them.
*** a/src/include/nodes/relation.h
--- b/src/include/nodes/relation.h
***************
*** 145,156 **** typedef struct PlannerGlobal
--- 145,162 ----
bool parallelModeNeeded; /* parallel mode actually required? */
char maxParallelHazard; /* worst PROPARALLEL hazard level */
+
+ Bitmapset *foreignParamIDs; /* PARAM_EXEC Params generated by FDWs */
} PlannerGlobal;
/* macro for fetching the Plan associated with a SubPlan node */
#define planner_subplan_get_plan(root, subplan) \
((Plan *) list_nth((root)->glob->subplans, (subplan)->plan_id - 1))
+ /* macro for checking if a Param is a PARAM_EXEC Param generated by an FDW */
+ #define IS_FOREIGN_PARAM(root, param) \
+ ((param)->paramkind == PARAM_EXEC && \
+ bms_is_member((param)->paramid, (root)->glob->foreignParamIDs))
/*----------
* PlannerInfo
*** a/src/include/optimizer/prep.h
--- b/src/include/optimizer/prep.h
***************
*** 40,45 **** extern Expr *canonicalize_qual(Expr *qual, bool is_check);
--- 40,48 ----
*/
extern List *preprocess_targetlist(PlannerInfo *root);
+ extern Param *generate_foreign_param(Oid paramtype, int32 paramtypmod,
+ Oid paramcollation);
+
extern PlanRowMark *get_plan_rowmark(List *rowmarks, Index rtindex);
/*