Directly modify foreign tables.

postgres_fdw can now sent an UPDATE or DELETE statement directly to
the foreign server in simple cases, rather than sending a SELECT FOR
UPDATE statement and then updating or deleting rows one-by-one.

Etsuro Fujita, reviewed by Rushabh Lathia, Shigeru Hanada, Kyotaro
Horiguchi, Albe Laurenz, Thom Brown, and me.
This commit is contained in:
Robert Haas 2016-03-18 13:48:58 -04:00
parent 3422feccca
commit 0bf3ae88af
21 changed files with 1515 additions and 125 deletions

View File

@ -1314,6 +1314,69 @@ deparseUpdateSql(StringInfo buf, PlannerInfo *root,
returningList, retrieved_attrs); returningList, retrieved_attrs);
} }
/*
* deparse remote UPDATE statement
*
* The statement text is appended to buf, and we also create an integer List
* of the columns being retrieved by RETURNING (if any), which is returned
* to *retrieved_attrs.
*/
void
deparseDirectUpdateSql(StringInfo buf, PlannerInfo *root,
Index rtindex, Relation rel,
List *targetlist,
List *targetAttrs,
List *remote_conds,
List **params_list,
List *returningList,
List **retrieved_attrs)
{
RelOptInfo *baserel = root->simple_rel_array[rtindex];
deparse_expr_cxt context;
int nestlevel;
bool first;
ListCell *lc;
/* Set up context struct for recursion */
context.root = root;
context.foreignrel = baserel;
context.buf = buf;
context.params_list = params_list;
appendStringInfoString(buf, "UPDATE ");
deparseRelation(buf, rel);
appendStringInfoString(buf, " SET ");
/* Make sure any constants in the exprs are printed portably */
nestlevel = set_transmission_modes();
first = true;
foreach(lc, targetAttrs)
{
int attnum = lfirst_int(lc);
TargetEntry *tle = get_tle_by_resno(targetlist, attnum);
if (!first)
appendStringInfoString(buf, ", ");
first = false;
deparseColumnRef(buf, rtindex, attnum, root, false);
appendStringInfoString(buf, " = ");
deparseExpr((Expr *) tle->expr, &context);
}
reset_transmission_modes(nestlevel);
if (remote_conds)
{
appendStringInfo(buf, " WHERE ");
appendConditions(remote_conds, &context);
}
deparseReturningList(buf, root, rtindex, rel, false,
returningList, retrieved_attrs);
}
/* /*
* deparse remote DELETE statement * deparse remote DELETE statement
* *
@ -1336,6 +1399,43 @@ deparseDeleteSql(StringInfo buf, PlannerInfo *root,
returningList, retrieved_attrs); returningList, retrieved_attrs);
} }
/*
* deparse remote DELETE statement
*
* The statement text is appended to buf, and we also create an integer List
* of the columns being retrieved by RETURNING (if any), which is returned
* to *retrieved_attrs.
*/
void
deparseDirectDeleteSql(StringInfo buf, PlannerInfo *root,
Index rtindex, Relation rel,
List *remote_conds,
List **params_list,
List *returningList,
List **retrieved_attrs)
{
RelOptInfo *baserel = root->simple_rel_array[rtindex];
deparse_expr_cxt context;
/* Set up context struct for recursion */
context.root = root;
context.foreignrel = baserel;
context.buf = buf;
context.params_list = params_list;
appendStringInfoString(buf, "DELETE FROM ");
deparseRelation(buf, rel);
if (remote_conds)
{
appendStringInfo(buf, " WHERE ");
appendConditions(remote_conds, &context);
}
deparseReturningList(buf, root, rtindex, rel, false,
returningList, retrieved_attrs);
}
/* /*
* Add a RETURNING clause, if needed, to an INSERT/UPDATE/DELETE. * Add a RETURNING clause, if needed, to an INSERT/UPDATE/DELETE.
*/ */

View File

@ -2259,7 +2259,26 @@ INSERT INTO ft2 (c1,c2,c3)
(3 rows) (3 rows)
INSERT INTO ft2 (c1,c2,c3) VALUES (1104,204,'ddd'), (1105,205,'eee'); INSERT INTO ft2 (c1,c2,c3) VALUES (1104,204,'ddd'), (1105,205,'eee');
EXPLAIN (verbose, costs off)
UPDATE ft2 SET c2 = c2 + 300, c3 = c3 || '_update3' WHERE c1 % 10 = 3; -- can be pushed down
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------
Update on public.ft2
-> Foreign Update on public.ft2
Remote SQL: UPDATE "S 1"."T 1" SET c2 = (c2 + 300), c3 = (c3 || '_update3'::text) WHERE ((("C 1" % 10) = 3))
(3 rows)
UPDATE ft2 SET c2 = c2 + 300, c3 = c3 || '_update3' WHERE c1 % 10 = 3; UPDATE ft2 SET c2 = c2 + 300, c3 = c3 || '_update3' WHERE c1 % 10 = 3;
EXPLAIN (verbose, costs off)
UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *; -- can be pushed down
QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------------------------------
Update on public.ft2
Output: c1, c2, c3, c4, c5, c6, c7, c8
-> Foreign Update on public.ft2
Remote SQL: UPDATE "S 1"."T 1" SET c2 = (c2 + 400), c3 = (c3 || '_update7'::text) WHERE ((("C 1" % 10) = 7)) RETURNING "C 1", c2, c3, c4, c5, c6, c7, c8
(4 rows)
UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *; UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *;
c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8
------+-----+--------------------+------------------------------+--------------------------+----+------------+----- ------+-----+--------------------+------------------------------+--------------------------+----+------------+-----
@ -2369,7 +2388,7 @@ UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING
EXPLAIN (verbose, costs off) EXPLAIN (verbose, costs off)
UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT
FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9; FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9; -- can't be pushed down
QUERY PLAN QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Update on public.ft2 Update on public.ft2
@ -2394,16 +2413,14 @@ UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT
UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT
FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9; FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9;
EXPLAIN (verbose, costs off) EXPLAIN (verbose, costs off)
DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4; DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4; -- can be pushed down
QUERY PLAN QUERY PLAN
---------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------------
Delete on public.ft2 Delete on public.ft2
Output: c1, c4 Output: c1, c4
Remote SQL: DELETE FROM "S 1"."T 1" WHERE ctid = $1 RETURNING "C 1", c4 -> Foreign Delete on public.ft2
-> Foreign Scan on public.ft2 Remote SQL: DELETE FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 5)) RETURNING "C 1", c4
Output: ctid (4 rows)
Remote SQL: SELECT ctid FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 5)) FOR UPDATE
(6 rows)
DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4; DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4;
c1 | c4 c1 | c4
@ -2514,7 +2531,7 @@ DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4;
(103 rows) (103 rows)
EXPLAIN (verbose, costs off) EXPLAIN (verbose, costs off)
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; -- can't be pushed down
QUERY PLAN QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Delete on public.ft2 Delete on public.ft2
@ -3379,16 +3396,14 @@ INSERT INTO ft2 (c1,c2,c3) VALUES (9999,999,'foo') RETURNING tableoid::regclass;
(1 row) (1 row)
EXPLAIN (verbose, costs off) 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; -- can be pushed down
QUERY PLAN QUERY PLAN
------------------------------------------------------------------------------------------------------------------- ------------------------------------------------------------------------------------
Update on public.ft2 Update on public.ft2
Output: (tableoid)::regclass Output: (tableoid)::regclass
Remote SQL: UPDATE "S 1"."T 1" SET c3 = $2 WHERE ctid = $1 -> Foreign Update on public.ft2
-> Foreign Scan on public.ft2 Remote SQL: UPDATE "S 1"."T 1" SET c3 = 'bar'::text WHERE (("C 1" = 9999))
Output: c1, c2, NULL::integer, 'bar'::text, c4, c5, c6, c7, c8, ctid (4 rows)
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; UPDATE ft2 SET c3 = 'bar' WHERE c1 = 9999 RETURNING tableoid::regclass;
tableoid tableoid
@ -3397,16 +3412,14 @@ UPDATE ft2 SET c3 = 'bar' WHERE c1 = 9999 RETURNING tableoid::regclass;
(1 row) (1 row)
EXPLAIN (verbose, costs off) EXPLAIN (verbose, costs off)
DELETE FROM ft2 WHERE c1 = 9999 RETURNING tableoid::regclass; DELETE FROM ft2 WHERE c1 = 9999 RETURNING tableoid::regclass; -- can be pushed down
QUERY PLAN QUERY PLAN
------------------------------------------------------------------------------------ --------------------------------------------------------------------
Delete on public.ft2 Delete on public.ft2
Output: (tableoid)::regclass Output: (tableoid)::regclass
Remote SQL: DELETE FROM "S 1"."T 1" WHERE ctid = $1 -> Foreign Delete on public.ft2
-> Foreign Scan on public.ft2 Remote SQL: DELETE FROM "S 1"."T 1" WHERE (("C 1" = 9999))
Output: ctid (4 rows)
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; DELETE FROM ft2 WHERE c1 = 9999 RETURNING tableoid::regclass;
tableoid tableoid
@ -3560,7 +3573,7 @@ CONTEXT: Remote SQL command: INSERT INTO "S 1"."T 1"("C 1", c2, c3, c4, c5, c6,
UPDATE ft1 SET c2 = -c2 WHERE c1 = 1; -- c2positive UPDATE ft1 SET c2 = -c2 WHERE c1 = 1; -- c2positive
ERROR: new row for relation "T 1" violates check constraint "c2positive" ERROR: new row for relation "T 1" violates check constraint "c2positive"
DETAIL: Failing row contains (1, -1, 00001_trig_update, 1970-01-02 08:00:00+00, 1970-01-02 00:00:00, 1, 1 , foo). DETAIL: Failing row contains (1, -1, 00001_trig_update, 1970-01-02 08:00:00+00, 1970-01-02 00:00:00, 1, 1 , foo).
CONTEXT: Remote SQL command: UPDATE "S 1"."T 1" SET c2 = $2 WHERE ctid = $1 CONTEXT: Remote SQL command: UPDATE "S 1"."T 1" SET c2 = (- c2) WHERE (("C 1" = 1))
-- Test savepoint/rollback behavior -- Test savepoint/rollback behavior
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
c2 | count c2 | count
@ -3719,7 +3732,7 @@ savepoint s3;
update ft2 set c2 = -2 where c2 = 42 and c1 = 10; -- fail on remote side update ft2 set c2 = -2 where c2 = 42 and c1 = 10; -- fail on remote side
ERROR: new row for relation "T 1" violates check constraint "c2positive" ERROR: new row for relation "T 1" violates check constraint "c2positive"
DETAIL: Failing row contains (10, -2, 00010_trig_update_trig_update, 1970-01-11 08:00:00+00, 1970-01-11 00:00:00, 0, 0 , foo). DETAIL: Failing row contains (10, -2, 00010_trig_update_trig_update, 1970-01-11 08:00:00+00, 1970-01-11 00:00:00, 0, 0 , foo).
CONTEXT: Remote SQL command: UPDATE "S 1"."T 1" SET c2 = $2 WHERE ctid = $1 CONTEXT: Remote SQL command: UPDATE "S 1"."T 1" SET c2 = (-2) WHERE ((c2 = 42)) AND (("C 1" = 10))
rollback to savepoint s3; rollback to savepoint s3;
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1; select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
c2 | count c2 | count
@ -3939,7 +3952,7 @@ CONTEXT: Remote SQL command: INSERT INTO "S 1"."T 1"("C 1", c2, c3, c4, c5, c6,
UPDATE ft1 SET c2 = -c2 WHERE c1 = 1; -- c2positive UPDATE ft1 SET c2 = -c2 WHERE c1 = 1; -- c2positive
ERROR: new row for relation "T 1" violates check constraint "c2positive" ERROR: new row for relation "T 1" violates check constraint "c2positive"
DETAIL: Failing row contains (1, -1, 00001_trig_update, 1970-01-02 08:00:00+00, 1970-01-02 00:00:00, 1, 1 , foo). DETAIL: Failing row contains (1, -1, 00001_trig_update, 1970-01-02 08:00:00+00, 1970-01-02 00:00:00, 1, 1 , foo).
CONTEXT: Remote SQL command: UPDATE "S 1"."T 1" SET c2 = $2 WHERE ctid = $1 CONTEXT: Remote SQL command: UPDATE "S 1"."T 1" SET c2 = (- c2) WHERE (("C 1" = 1))
ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c2positive; ALTER FOREIGN TABLE ft1 DROP CONSTRAINT ft1_c2positive;
-- But inconsistent check constraints provide inconsistent results -- But inconsistent check constraints provide inconsistent results
ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c2negative CHECK (c2 < 0); ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c2negative CHECK (c2 < 0);
@ -4332,6 +4345,199 @@ NOTICE: NEW: (13,"test triggered !")
(0,27) (0,27)
(1 row) (1 row)
-- cleanup
DROP TRIGGER trig_row_before ON rem1;
DROP TRIGGER trig_row_after ON rem1;
DROP TRIGGER trig_local_before ON loc1;
-- Test direct foreign table modification functionality
-- Test with statement-level triggers
CREATE TRIGGER trig_stmt_before
BEFORE DELETE OR INSERT OR UPDATE ON rem1
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
EXPLAIN (verbose, costs off)
UPDATE rem1 set f2 = ''; -- can be pushed down
QUERY PLAN
----------------------------------------------------------
Update on public.rem1
-> Foreign Update on public.rem1
Remote SQL: UPDATE public.loc1 SET f2 = ''::text
(3 rows)
EXPLAIN (verbose, costs off)
DELETE FROM rem1; -- can be pushed down
QUERY PLAN
---------------------------------------------
Delete on public.rem1
-> Foreign Delete on public.rem1
Remote SQL: DELETE FROM public.loc1
(3 rows)
DROP TRIGGER trig_stmt_before ON rem1;
CREATE TRIGGER trig_stmt_after
AFTER DELETE OR INSERT OR UPDATE ON rem1
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
EXPLAIN (verbose, costs off)
UPDATE rem1 set f2 = ''; -- can be pushed down
QUERY PLAN
----------------------------------------------------------
Update on public.rem1
-> Foreign Update on public.rem1
Remote SQL: UPDATE public.loc1 SET f2 = ''::text
(3 rows)
EXPLAIN (verbose, costs off)
DELETE FROM rem1; -- can be pushed down
QUERY PLAN
---------------------------------------------
Delete on public.rem1
-> Foreign Delete on public.rem1
Remote SQL: DELETE FROM public.loc1
(3 rows)
DROP TRIGGER trig_stmt_after ON rem1;
-- Test with row-level ON INSERT triggers
CREATE TRIGGER trig_row_before_insert
BEFORE INSERT ON rem1
FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
EXPLAIN (verbose, costs off)
UPDATE rem1 set f2 = ''; -- can be pushed down
QUERY PLAN
----------------------------------------------------------
Update on public.rem1
-> Foreign Update on public.rem1
Remote SQL: UPDATE public.loc1 SET f2 = ''::text
(3 rows)
EXPLAIN (verbose, costs off)
DELETE FROM rem1; -- can be pushed down
QUERY PLAN
---------------------------------------------
Delete on public.rem1
-> Foreign Delete on public.rem1
Remote SQL: DELETE FROM public.loc1
(3 rows)
DROP TRIGGER trig_row_before_insert ON rem1;
CREATE TRIGGER trig_row_after_insert
AFTER INSERT ON rem1
FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
EXPLAIN (verbose, costs off)
UPDATE rem1 set f2 = ''; -- can be pushed down
QUERY PLAN
----------------------------------------------------------
Update on public.rem1
-> Foreign Update on public.rem1
Remote SQL: UPDATE public.loc1 SET f2 = ''::text
(3 rows)
EXPLAIN (verbose, costs off)
DELETE FROM rem1; -- can be pushed down
QUERY PLAN
---------------------------------------------
Delete on public.rem1
-> Foreign Delete on public.rem1
Remote SQL: DELETE FROM public.loc1
(3 rows)
DROP TRIGGER trig_row_after_insert ON rem1;
-- Test with row-level ON UPDATE triggers
CREATE TRIGGER trig_row_before_update
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)
DELETE FROM rem1; -- can be pushed down
QUERY PLAN
---------------------------------------------
Delete on public.rem1
-> Foreign Delete on public.rem1
Remote SQL: DELETE FROM public.loc1
(3 rows)
DROP TRIGGER trig_row_before_update ON rem1;
CREATE TRIGGER trig_row_after_update
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)
DELETE FROM rem1; -- can be pushed down
QUERY PLAN
---------------------------------------------
Delete on public.rem1
-> Foreign Delete on public.rem1
Remote SQL: DELETE FROM public.loc1
(3 rows)
DROP TRIGGER trig_row_after_update ON rem1;
-- Test with row-level ON DELETE triggers
CREATE TRIGGER trig_row_before_delete
BEFORE DELETE ON rem1
FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
EXPLAIN (verbose, costs off)
UPDATE rem1 set f2 = ''; -- can be pushed down
QUERY PLAN
----------------------------------------------------------
Update on public.rem1
-> Foreign Update on public.rem1
Remote SQL: UPDATE public.loc1 SET f2 = ''::text
(3 rows)
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;
CREATE TRIGGER trig_row_after_delete
AFTER DELETE ON rem1
FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
EXPLAIN (verbose, costs off)
UPDATE rem1 set f2 = ''; -- can be pushed down
QUERY PLAN
----------------------------------------------------------
Update on public.rem1
-> Foreign Update on public.rem1
Remote SQL: UPDATE public.loc1 SET f2 = ''::text
(3 rows)
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;
-- =================================================================== -- ===================================================================
-- test inheritance features -- test inheritance features
-- =================================================================== -- ===================================================================
@ -4801,6 +5007,56 @@ fetch from c;
update bar set f2 = null where current of c; update bar set f2 = null where current of c;
ERROR: WHERE CURRENT OF is not supported for this table type ERROR: WHERE CURRENT OF is not supported for this table type
rollback; rollback;
explain (verbose, costs off)
delete from foo where f1 < 5 returning *;
QUERY PLAN
--------------------------------------------------------------------------------
Delete on public.foo
Output: foo.f1, foo.f2
Delete on public.foo
Foreign Delete on public.foo2
-> Index Scan using i_foo_f1 on public.foo
Output: foo.ctid
Index Cond: (foo.f1 < 5)
-> Foreign Delete on public.foo2
Remote SQL: DELETE FROM public.loct1 WHERE ((f1 < 5)) RETURNING f1, f2
(9 rows)
delete from foo where f1 < 5 returning *;
f1 | f2
----+----
1 | 1
3 | 3
0 | 0
2 | 2
4 | 4
(5 rows)
explain (verbose, costs off)
update bar set f2 = f2 + 100 returning *;
QUERY PLAN
------------------------------------------------------------------------------
Update on public.bar
Output: bar.f1, bar.f2
Update on public.bar
Foreign Update on public.bar2
-> Seq Scan on public.bar
Output: bar.f1, (bar.f2 + 100), bar.ctid
-> Foreign Update on public.bar2
Remote SQL: UPDATE public.loct2 SET f2 = (f2 + 100) RETURNING f1, f2
(8 rows)
update bar set f2 = f2 + 100 returning *;
f1 | f2
----+-----
1 | 311
2 | 322
6 | 266
3 | 333
4 | 344
7 | 277
(6 rows)
drop table foo cascade; drop table foo cascade;
NOTICE: drop cascades to foreign table foo2 NOTICE: drop cascades to foreign table foo2
drop table bar cascade; drop table bar cascade;

View File

@ -61,6 +61,8 @@ enum FdwScanPrivateIndex
{ {
/* SQL statement to execute remotely (as a String node) */ /* SQL statement to execute remotely (as a String node) */
FdwScanPrivateSelectSql, FdwScanPrivateSelectSql,
/* List of restriction clauses that can be executed remotely */
FdwScanPrivateRemoteConds,
/* Integer list of attribute numbers retrieved by the SELECT */ /* Integer list of attribute numbers retrieved by the SELECT */
FdwScanPrivateRetrievedAttrs, FdwScanPrivateRetrievedAttrs,
/* Integer representing the desired fetch_size */ /* Integer representing the desired fetch_size */
@ -97,6 +99,27 @@ enum FdwModifyPrivateIndex
FdwModifyPrivateRetrievedAttrs FdwModifyPrivateRetrievedAttrs
}; };
/*
* Similarly, this enum describes what's kept in the fdw_private list for
* a ForeignScan node that modifies a foreign table directly. We store:
*
* 1) UPDATE/DELETE statement text to be sent to the remote server
* 2) Boolean flag showing if the remote query has a RETURNING clause
* 3) Integer list of attribute numbers retrieved by RETURNING, if any
* 4) Boolean flag showing if we set the command es_processed
*/
enum FdwDirectModifyPrivateIndex
{
/* SQL statement to execute remotely (as a String node) */
FdwDirectModifyPrivateUpdateSql,
/* has-returning flag (as an integer Value node) */
FdwDirectModifyPrivateHasReturning,
/* Integer list of attribute numbers retrieved by RETURNING */
FdwDirectModifyPrivateRetrievedAttrs,
/* set-processed flag (as an integer Value node) */
FdwDirectModifyPrivateSetProcessed
};
/* /*
* Execution state of a foreign scan using postgres_fdw. * Execution state of a foreign scan using postgres_fdw.
*/ */
@ -163,6 +186,36 @@ typedef struct PgFdwModifyState
MemoryContext temp_cxt; /* context for per-tuple temporary data */ MemoryContext temp_cxt; /* context for per-tuple temporary data */
} PgFdwModifyState; } PgFdwModifyState;
/*
* Execution state of a foreign scan that modifies a foreign table directly.
*/
typedef struct PgFdwDirectModifyState
{
Relation rel; /* relcache entry for the foreign table */
AttInMetadata *attinmeta; /* attribute datatype conversion metadata */
/* extracted fdw_private data */
char *query; /* text of UPDATE/DELETE command */
bool has_returning; /* is there a RETURNING clause? */
List *retrieved_attrs; /* attr numbers retrieved by RETURNING */
bool set_processed; /* do we set the command es_processed? */
/* for remote query execution */
PGconn *conn; /* connection for the update */
int numParams; /* number of parameters passed to query */
FmgrInfo *param_flinfo; /* output conversion functions for them */
List *param_exprs; /* executable expressions for param values */
const char **param_values; /* textual values of query parameters */
/* for storing result tuples */
PGresult *result; /* result for query */
int num_tuples; /* # of result tuples */
int next_tuple; /* index of next one to return */
/* working memory context */
MemoryContext temp_cxt; /* context for per-tuple temporary data */
} PgFdwDirectModifyState;
/* /*
* Workspace for analyzing a foreign table. * Workspace for analyzing a foreign table.
*/ */
@ -263,6 +316,13 @@ static TupleTableSlot *postgresExecForeignDelete(EState *estate,
static void postgresEndForeignModify(EState *estate, static void postgresEndForeignModify(EState *estate,
ResultRelInfo *resultRelInfo); ResultRelInfo *resultRelInfo);
static int postgresIsForeignRelUpdatable(Relation rel); static int postgresIsForeignRelUpdatable(Relation rel);
static bool postgresPlanDirectModify(PlannerInfo *root,
ModifyTable *plan,
Index resultRelation,
int subplan_index);
static void postgresBeginDirectModify(ForeignScanState *node, int eflags);
static TupleTableSlot *postgresIterateDirectModify(ForeignScanState *node);
static void postgresEndDirectModify(ForeignScanState *node);
static void postgresExplainForeignScan(ForeignScanState *node, static void postgresExplainForeignScan(ForeignScanState *node,
ExplainState *es); ExplainState *es);
static void postgresExplainForeignModify(ModifyTableState *mtstate, static void postgresExplainForeignModify(ModifyTableState *mtstate,
@ -270,6 +330,8 @@ static void postgresExplainForeignModify(ModifyTableState *mtstate,
List *fdw_private, List *fdw_private,
int subplan_index, int subplan_index,
ExplainState *es); ExplainState *es);
static void postgresExplainDirectModify(ForeignScanState *node,
ExplainState *es);
static bool postgresAnalyzeForeignTable(Relation relation, static bool postgresAnalyzeForeignTable(Relation relation,
AcquireSampleRowsFunc *func, AcquireSampleRowsFunc *func,
BlockNumber *totalpages); BlockNumber *totalpages);
@ -311,6 +373,18 @@ static const char **convert_prep_stmt_params(PgFdwModifyState *fmstate,
TupleTableSlot *slot); TupleTableSlot *slot);
static void store_returning_result(PgFdwModifyState *fmstate, static void store_returning_result(PgFdwModifyState *fmstate,
TupleTableSlot *slot, PGresult *res); TupleTableSlot *slot, PGresult *res);
static void execute_dml_stmt(ForeignScanState *node);
static TupleTableSlot *get_returning_data(ForeignScanState *node);
static void prepare_query_params(PlanState *node,
List *fdw_exprs,
int numParams,
FmgrInfo **param_flinfo,
List **param_exprs,
const char ***param_values);
static void process_query_params(ExprContext *econtext,
FmgrInfo *param_flinfo,
List *param_exprs,
const char **param_values);
static int postgresAcquireSampleRowsFunc(Relation relation, int elevel, static int postgresAcquireSampleRowsFunc(Relation relation, int elevel,
HeapTuple *rows, int targrows, HeapTuple *rows, int targrows,
double *totalrows, double *totalrows,
@ -362,12 +436,17 @@ postgres_fdw_handler(PG_FUNCTION_ARGS)
routine->ExecForeignDelete = postgresExecForeignDelete; routine->ExecForeignDelete = postgresExecForeignDelete;
routine->EndForeignModify = postgresEndForeignModify; routine->EndForeignModify = postgresEndForeignModify;
routine->IsForeignRelUpdatable = postgresIsForeignRelUpdatable; routine->IsForeignRelUpdatable = postgresIsForeignRelUpdatable;
routine->PlanDirectModify = postgresPlanDirectModify;
routine->BeginDirectModify = postgresBeginDirectModify;
routine->IterateDirectModify = postgresIterateDirectModify;
routine->EndDirectModify = postgresEndDirectModify;
/* Function for EvalPlanQual rechecks */ /* Function for EvalPlanQual rechecks */
routine->RecheckForeignScan = postgresRecheckForeignScan; routine->RecheckForeignScan = postgresRecheckForeignScan;
/* Support functions for EXPLAIN */ /* Support functions for EXPLAIN */
routine->ExplainForeignScan = postgresExplainForeignScan; routine->ExplainForeignScan = postgresExplainForeignScan;
routine->ExplainForeignModify = postgresExplainForeignModify; routine->ExplainForeignModify = postgresExplainForeignModify;
routine->ExplainDirectModify = postgresExplainDirectModify;
/* Support functions for ANALYZE */ /* Support functions for ANALYZE */
routine->AnalyzeForeignTable = postgresAnalyzeForeignTable; routine->AnalyzeForeignTable = postgresAnalyzeForeignTable;
@ -1122,7 +1201,8 @@ postgresGetForeignPlan(PlannerInfo *root,
* Build the fdw_private list that will be available to the executor. * Build the fdw_private list that will be available to the executor.
* Items in the list must match order in enum FdwScanPrivateIndex. * Items in the list must match order in enum FdwScanPrivateIndex.
*/ */
fdw_private = list_make4(makeString(sql.data), fdw_private = list_make5(makeString(sql.data),
remote_conds,
retrieved_attrs, retrieved_attrs,
makeInteger(fpinfo->fetch_size), makeInteger(fpinfo->fetch_size),
makeInteger(foreignrel->umid)); makeInteger(foreignrel->umid));
@ -1159,8 +1239,6 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
PgFdwScanState *fsstate; PgFdwScanState *fsstate;
UserMapping *user; UserMapping *user;
int numParams; int numParams;
int i;
ListCell *lc;
/* /*
* Do nothing in EXPLAIN (no ANALYZE) case. node->fdw_state stays NULL. * Do nothing in EXPLAIN (no ANALYZE) case. node->fdw_state stays NULL.
@ -1247,42 +1325,18 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
fsstate->attinmeta = TupleDescGetAttInMetadata(fsstate->tupdesc); fsstate->attinmeta = TupleDescGetAttInMetadata(fsstate->tupdesc);
/* Prepare for output conversion of parameters used in remote query. */ /*
* Prepare for processing of parameters used in remote query, if any.
*/
numParams = list_length(fsplan->fdw_exprs); numParams = list_length(fsplan->fdw_exprs);
fsstate->numParams = numParams; fsstate->numParams = numParams;
fsstate->param_flinfo = (FmgrInfo *) palloc0(sizeof(FmgrInfo) * numParams);
i = 0;
foreach(lc, fsplan->fdw_exprs)
{
Node *param_expr = (Node *) lfirst(lc);
Oid typefnoid;
bool isvarlena;
getTypeOutputInfo(exprType(param_expr), &typefnoid, &isvarlena);
fmgr_info(typefnoid, &fsstate->param_flinfo[i]);
i++;
}
/*
* Prepare remote-parameter expressions for evaluation. (Note: in
* practice, we expect that all these expressions will be just Params, so
* we could possibly do something more efficient than using the full
* expression-eval machinery for this. But probably there would be little
* benefit, and it'd require postgres_fdw to know more than is desirable
* about Param evaluation.)
*/
fsstate->param_exprs = (List *)
ExecInitExpr((Expr *) fsplan->fdw_exprs,
(PlanState *) node);
/*
* Allocate buffer for text form of query parameters, if any.
*/
if (numParams > 0) if (numParams > 0)
fsstate->param_values = (const char **) palloc0(numParams * sizeof(char *)); prepare_query_params((PlanState *) node,
else fsplan->fdw_exprs,
fsstate->param_values = NULL; numParams,
&fsstate->param_flinfo,
&fsstate->param_exprs,
&fsstate->param_values);
} }
/* /*
@ -1447,13 +1501,6 @@ postgresAddForeignUpdateTargets(Query *parsetree,
/* /*
* postgresPlanForeignModify * postgresPlanForeignModify
* Plan an insert/update/delete operation on a foreign table * Plan an insert/update/delete operation on a foreign table
*
* Note: currently, the plan tree generated for UPDATE/DELETE will always
* include a ForeignScan that retrieves ctids (using SELECT FOR UPDATE)
* and then the ModifyTable node will have to execute individual remote
* UPDATE/DELETE commands. If there are no local conditions or joins
* needed, it'd be better to let the scan node do UPDATE/DELETE RETURNING
* and then do nothing at ModifyTable. Room for future optimization ...
*/ */
static List * static List *
postgresPlanForeignModify(PlannerInfo *root, postgresPlanForeignModify(PlannerInfo *root,
@ -1991,6 +2038,314 @@ postgresRecheckForeignScan(ForeignScanState *node, TupleTableSlot *slot)
return true; return true;
} }
/*
* postgresPlanDirectModify
* Consider a direct foreign table modification
*
* Decide whether it is safe to modify a foreign table directly, and if so,
* rewrite subplan accordingly.
*/
static bool
postgresPlanDirectModify(PlannerInfo *root,
ModifyTable *plan,
Index resultRelation,
int subplan_index)
{
CmdType operation = plan->operation;
Plan *subplan = (Plan *) list_nth(plan->plans, subplan_index);
RangeTblEntry *rte = planner_rt_fetch(resultRelation, root);
Relation rel;
StringInfoData sql;
ForeignScan *fscan;
List *targetAttrs = NIL;
List *remote_conds;
List *params_list = NIL;
List *returningList = NIL;
List *retrieved_attrs = NIL;
/*
* Decide whether it is safe to modify a foreign table directly.
*/
/*
* The table modification must be an UPDATE or DELETE.
*/
if (operation != CMD_UPDATE && operation != CMD_DELETE)
return false;
/*
* It's unsafe to modify a foreign table directly if there are any local
* joins needed.
*/
if (!IsA(subplan, ForeignScan))
return false;
/*
* It's unsafe to modify a foreign table directly if there are any quals
* that should be evaluated locally.
*/
if (subplan->qual != NIL)
return false;
/*
* We can't handle an UPDATE or DELETE on a foreign join for now.
*/
fscan = (ForeignScan *) subplan;
if (fscan->scan.scanrelid == 0)
return false;
/*
* It's unsafe to update a foreign table directly, if any expressions to
* assign to the target columns are unsafe to evaluate remotely.
*/
if (operation == CMD_UPDATE)
{
RelOptInfo *baserel = root->simple_rel_array[resultRelation];
int col;
/*
* We transmit only columns that were explicitly targets of the UPDATE,
* so as to avoid unnecessary data transmission.
*/
col = -1;
while ((col = bms_next_member(rte->updatedCols, col)) >= 0)
{
/* bit numbers are offset by FirstLowInvalidHeapAttributeNumber */
AttrNumber attno = col + FirstLowInvalidHeapAttributeNumber;
TargetEntry *tle;
if (attno <= InvalidAttrNumber) /* shouldn't happen */
elog(ERROR, "system-column update is not supported");
tle = get_tle_by_resno(subplan->targetlist, attno);
if (!is_foreign_expr(root, baserel, (Expr *) tle->expr))
return false;
targetAttrs = lappend_int(targetAttrs, attno);
}
}
/*
* Ok, rewrite subplan so as to modify the foreign table directly.
*/
initStringInfo(&sql);
/*
* Core code already has some lock on each rel being planned, so we can
* use NoLock here.
*/
rel = heap_open(rte->relid, NoLock);
/*
* Extract the baserestrictinfo clauses that can be evaluated remotely.
*/
remote_conds = (List *) list_nth(fscan->fdw_private,
FdwScanPrivateRemoteConds);
/*
* Extract the relevant RETURNING list if any.
*/
if (plan->returningLists)
returningList = (List *) list_nth(plan->returningLists, subplan_index);
/*
* Construct the SQL command string.
*/
switch (operation)
{
case CMD_UPDATE:
deparseDirectUpdateSql(&sql, root, resultRelation, rel,
((Plan *) fscan)->targetlist,
targetAttrs,
remote_conds, &params_list,
returningList, &retrieved_attrs);
break;
case CMD_DELETE:
deparseDirectDeleteSql(&sql, root, resultRelation, rel,
remote_conds, &params_list,
returningList, &retrieved_attrs);
break;
default:
elog(ERROR, "unexpected operation: %d", (int) operation);
break;
}
/*
* Update the operation info.
*/
fscan->operation = operation;
/*
* Update the fdw_exprs list that will be available to the executor.
*/
fscan->fdw_exprs = params_list;
/*
* Update the fdw_private list that will be available to the executor.
* Items in the list must match enum FdwDirectModifyPrivateIndex, above.
*/
fscan->fdw_private = list_make4(makeString(sql.data),
makeInteger((retrieved_attrs != NIL)),
retrieved_attrs,
makeInteger(plan->canSetTag));
heap_close(rel, NoLock);
return true;
}
/*
* postgresBeginDirectModify
* Prepare a direct foreign table modification
*/
static void
postgresBeginDirectModify(ForeignScanState *node, int eflags)
{
ForeignScan *fsplan = (ForeignScan *) node->ss.ps.plan;
EState *estate = node->ss.ps.state;
PgFdwDirectModifyState *dmstate;
RangeTblEntry *rte;
Oid userid;
ForeignTable *table;
UserMapping *user;
int numParams;
/*
* Do nothing in EXPLAIN (no ANALYZE) case. node->fdw_state stays NULL.
*/
if (eflags & EXEC_FLAG_EXPLAIN_ONLY)
return;
/*
* We'll save private state in node->fdw_state.
*/
dmstate = (PgFdwDirectModifyState *) palloc0(sizeof(PgFdwDirectModifyState));
node->fdw_state = (void *) dmstate;
/*
* Identify which user to do the remote access as. This should match what
* ExecCheckRTEPerms() does.
*/
rte = rt_fetch(fsplan->scan.scanrelid, estate->es_range_table);
userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
/* Get info about foreign table. */
dmstate->rel = node->ss.ss_currentRelation;
table = GetForeignTable(RelationGetRelid(dmstate->rel));
user = GetUserMapping(userid, table->serverid);
/*
* Get connection to the foreign server. Connection manager will
* establish new connection if necessary.
*/
dmstate->conn = GetConnection(user, false);
/* Initialize state variable */
dmstate->num_tuples = -1; /* -1 means not set yet */
/* Get private info created by planner functions. */
dmstate->query = strVal(list_nth(fsplan->fdw_private,
FdwDirectModifyPrivateUpdateSql));
dmstate->has_returning = intVal(list_nth(fsplan->fdw_private,
FdwDirectModifyPrivateHasReturning));
dmstate->retrieved_attrs = (List *) list_nth(fsplan->fdw_private,
FdwDirectModifyPrivateRetrievedAttrs);
dmstate->set_processed = intVal(list_nth(fsplan->fdw_private,
FdwDirectModifyPrivateSetProcessed));
/* Create context for per-tuple temp workspace. */
dmstate->temp_cxt = AllocSetContextCreate(estate->es_query_cxt,
"postgres_fdw temporary data",
ALLOCSET_SMALL_MINSIZE,
ALLOCSET_SMALL_INITSIZE,
ALLOCSET_SMALL_MAXSIZE);
/* Prepare for input conversion of RETURNING results. */
if (dmstate->has_returning)
dmstate->attinmeta = TupleDescGetAttInMetadata(RelationGetDescr(dmstate->rel));
/*
* Prepare for processing of parameters used in remote query, if any.
*/
numParams = list_length(fsplan->fdw_exprs);
dmstate->numParams = numParams;
if (numParams > 0)
prepare_query_params((PlanState *) node,
fsplan->fdw_exprs,
numParams,
&dmstate->param_flinfo,
&dmstate->param_exprs,
&dmstate->param_values);
}
/*
* postgresIterateDirectModify
* Execute a direct foreign table modification
*/
static TupleTableSlot *
postgresIterateDirectModify(ForeignScanState *node)
{
PgFdwDirectModifyState *dmstate = (PgFdwDirectModifyState *) node->fdw_state;
EState *estate = node->ss.ps.state;
ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
/*
* If this is the first call after Begin, execute the statement.
*/
if (dmstate->num_tuples == -1)
execute_dml_stmt(node);
/*
* If the local query doesn't specify RETURNING, just clear tuple slot.
*/
if (!resultRelInfo->ri_projectReturning)
{
TupleTableSlot *slot = node->ss.ss_ScanTupleSlot;
Instrumentation *instr = node->ss.ps.instrument;
Assert(!dmstate->has_returning);
/* Increment the command es_processed count if necessary. */
if (dmstate->set_processed)
estate->es_processed += dmstate->num_tuples;
/* Increment the tuple count for EXPLAIN ANALYZE if necessary. */
if (instr)
instr->tuplecount += dmstate->num_tuples;
return ExecClearTuple(slot);
}
/*
* Get the next RETURNING tuple.
*/
return get_returning_data(node);
}
/*
* postgresEndDirectModify
* Finish a direct foreign table modification
*/
static void
postgresEndDirectModify(ForeignScanState *node)
{
PgFdwDirectModifyState *dmstate = (PgFdwDirectModifyState *) node->fdw_state;
/* if dmstate is NULL, we are in EXPLAIN; nothing to do */
if (dmstate == NULL)
return;
/* Release PGresult */
if (dmstate->result)
PQclear(dmstate->result);
/* Release remote connection */
ReleaseConnection(dmstate->conn);
dmstate->conn = NULL;
/* MemoryContext will be deleted automatically. */
}
/* /*
* postgresExplainForeignScan * postgresExplainForeignScan
* Produce extra output for EXPLAIN of a ForeignScan on a foreign table * Produce extra output for EXPLAIN of a ForeignScan on a foreign table
@ -2044,6 +2399,25 @@ postgresExplainForeignModify(ModifyTableState *mtstate,
} }
} }
/*
* postgresExplainDirectModify
* Produce extra output for EXPLAIN of a ForeignScan that modifies a
* foreign table directly
*/
static void
postgresExplainDirectModify(ForeignScanState *node, ExplainState *es)
{
List *fdw_private;
char *sql;
if (es->verbose)
{
fdw_private = ((ForeignScan *) node->ss.ps.plan)->fdw_private;
sql = strVal(list_nth(fdw_private, FdwDirectModifyPrivateUpdateSql));
ExplainPropertyText("Remote SQL", sql, es);
}
}
/* /*
* estimate_path_cost_size * estimate_path_cost_size
@ -2419,38 +2793,14 @@ create_cursor(ForeignScanState *node)
*/ */
if (numParams > 0) if (numParams > 0)
{ {
int nestlevel;
MemoryContext oldcontext; MemoryContext oldcontext;
int i;
ListCell *lc;
oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
nestlevel = set_transmission_modes(); process_query_params(econtext,
fsstate->param_flinfo,
i = 0; fsstate->param_exprs,
foreach(lc, fsstate->param_exprs) values);
{
ExprState *expr_state = (ExprState *) lfirst(lc);
Datum expr_value;
bool isNull;
/* Evaluate the parameter expression */
expr_value = ExecEvalExpr(expr_state, econtext, &isNull, NULL);
/*
* Get string representation of each parameter value by invoking
* type-specific output function, unless the value is null.
*/
if (isNull)
values[i] = NULL;
else
values[i] = OutputFunctionCall(&fsstate->param_flinfo[i],
expr_value);
i++;
}
reset_transmission_modes(nestlevel);
MemoryContextSwitchTo(oldcontext); MemoryContextSwitchTo(oldcontext);
} }
@ -2770,6 +3120,197 @@ store_returning_result(PgFdwModifyState *fmstate,
PG_END_TRY(); PG_END_TRY();
} }
/*
* Execute a direct UPDATE/DELETE statement.
*/
static void
execute_dml_stmt(ForeignScanState *node)
{
PgFdwDirectModifyState *dmstate = (PgFdwDirectModifyState *) node->fdw_state;
ExprContext *econtext = node->ss.ps.ps_ExprContext;
int numParams = dmstate->numParams;
const char **values = dmstate->param_values;
/*
* Construct array of query parameter values in text format.
*/
if (numParams > 0)
process_query_params(econtext,
dmstate->param_flinfo,
dmstate->param_exprs,
values);
/*
* Notice that we pass NULL for paramTypes, thus forcing the remote server
* to infer types for all parameters. Since we explicitly cast every
* parameter (see deparse.c), the "inference" is trivial and will produce
* the desired result. This allows us to avoid assuming that the remote
* server has the same OIDs we do for the parameters' types.
*
* We don't use a PG_TRY block here, so be careful not to throw error
* without releasing the PGresult.
*/
dmstate->result = PQexecParams(dmstate->conn, dmstate->query,
numParams, NULL, values, NULL, NULL, 0);
if (PQresultStatus(dmstate->result) !=
(dmstate->has_returning ? PGRES_TUPLES_OK : PGRES_COMMAND_OK))
pgfdw_report_error(ERROR, dmstate->result, dmstate->conn, true,
dmstate->query);
/* Get the number of rows affected. */
if (dmstate->has_returning)
dmstate->num_tuples = PQntuples(dmstate->result);
else
dmstate->num_tuples = atoi(PQcmdTuples(dmstate->result));
}
/*
* Get the result of a RETURNING clause.
*/
static TupleTableSlot *
get_returning_data(ForeignScanState *node)
{
PgFdwDirectModifyState *dmstate = (PgFdwDirectModifyState *) node->fdw_state;
EState *estate = node->ss.ps.state;
ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
TupleTableSlot *slot = node->ss.ss_ScanTupleSlot;
Assert(resultRelInfo->ri_projectReturning);
/* If we didn't get any tuples, must be end of data. */
if (dmstate->next_tuple >= dmstate->num_tuples)
return ExecClearTuple(slot);
/* Increment the command es_processed count if necessary. */
if (dmstate->set_processed)
estate->es_processed += 1;
/*
* Store a RETURNING tuple. If has_returning is false, just emit a dummy
* tuple. (has_returning is false when the local query is of the form
* "UPDATE/DELETE .. RETURNING 1" for example.)
*/
if (!dmstate->has_returning)
ExecStoreAllNullTuple(slot);
else
{
/*
* On error, be sure to release the PGresult on the way out. Callers
* do not have PG_TRY blocks to ensure this happens.
*/
PG_TRY();
{
HeapTuple newtup;
newtup = make_tuple_from_result_row(dmstate->result,
dmstate->next_tuple,
dmstate->rel,
dmstate->attinmeta,
dmstate->retrieved_attrs,
NULL,
dmstate->temp_cxt);
ExecStoreTuple(newtup, slot, InvalidBuffer, false);
}
PG_CATCH();
{
if (dmstate->result)
PQclear(dmstate->result);
PG_RE_THROW();
}
PG_END_TRY();
}
dmstate->next_tuple++;
/* Make slot available for evaluation of the local query RETURNING list. */
resultRelInfo->ri_projectReturning->pi_exprContext->ecxt_scantuple = slot;
return slot;
}
/*
* Prepare for processing of parameters used in remote query.
*/
static void
prepare_query_params(PlanState *node,
List *fdw_exprs,
int numParams,
FmgrInfo **param_flinfo,
List **param_exprs,
const char ***param_values)
{
int i;
ListCell *lc;
Assert(numParams > 0);
/* Prepare for output conversion of parameters used in remote query. */
*param_flinfo = (FmgrInfo *) palloc0(sizeof(FmgrInfo) * numParams);
i = 0;
foreach(lc, fdw_exprs)
{
Node *param_expr = (Node *) lfirst(lc);
Oid typefnoid;
bool isvarlena;
getTypeOutputInfo(exprType(param_expr), &typefnoid, &isvarlena);
fmgr_info(typefnoid, &(*param_flinfo)[i]);
i++;
}
/*
* Prepare remote-parameter expressions for evaluation. (Note: in
* practice, we expect that all these expressions will be just Params, so
* we could possibly do something more efficient than using the full
* expression-eval machinery for this. But probably there would be little
* benefit, and it'd require postgres_fdw to know more than is desirable
* about Param evaluation.)
*/
*param_exprs = (List *) ExecInitExpr((Expr *) fdw_exprs, node);
/* Allocate buffer for text form of query parameters. */
*param_values = (const char **) palloc0(numParams * sizeof(char *));
}
/*
* Construct array of query parameter values in text format.
*/
static void
process_query_params(ExprContext *econtext,
FmgrInfo *param_flinfo,
List *param_exprs,
const char **param_values)
{
int nestlevel;
int i;
ListCell *lc;
nestlevel = set_transmission_modes();
i = 0;
foreach(lc, param_exprs)
{
ExprState *expr_state = (ExprState *) lfirst(lc);
Datum expr_value;
bool isNull;
/* Evaluate the parameter expression */
expr_value = ExecEvalExpr(expr_state, econtext, &isNull, NULL);
/*
* Get string representation of each parameter value by invoking
* type-specific output function, unless the value is null.
*/
if (isNull)
param_values[i] = NULL;
else
param_values[i] = OutputFunctionCall(&param_flinfo[i], expr_value);
i++;
}
reset_transmission_modes(nestlevel);
}
/* /*
* postgresAnalyzeForeignTable * postgresAnalyzeForeignTable
* Test whether analyzing this foreign table is supported * Test whether analyzing this foreign table is supported

View File

@ -130,10 +130,24 @@ extern void deparseUpdateSql(StringInfo buf, PlannerInfo *root,
Index rtindex, Relation rel, Index rtindex, Relation rel,
List *targetAttrs, List *returningList, List *targetAttrs, List *returningList,
List **retrieved_attrs); List **retrieved_attrs);
extern void deparseDirectUpdateSql(StringInfo buf, PlannerInfo *root,
Index rtindex, Relation rel,
List *targetlist,
List *targetAttrs,
List *remote_conds,
List **params_list,
List *returningList,
List **retrieved_attrs);
extern void deparseDeleteSql(StringInfo buf, PlannerInfo *root, extern void deparseDeleteSql(StringInfo buf, PlannerInfo *root,
Index rtindex, Relation rel, Index rtindex, Relation rel,
List *returningList, List *returningList,
List **retrieved_attrs); List **retrieved_attrs);
extern void deparseDirectDeleteSql(StringInfo buf, PlannerInfo *root,
Index rtindex, Relation rel,
List *remote_conds,
List **params_list,
List *returningList,
List **retrieved_attrs);
extern void deparseAnalyzeSizeSql(StringInfo buf, Relation rel); extern void deparseAnalyzeSizeSql(StringInfo buf, Relation rel);
extern void deparseAnalyzeSql(StringInfo buf, Relation rel, extern void deparseAnalyzeSql(StringInfo buf, Relation rel,
List **retrieved_attrs); List **retrieved_attrs);

View File

@ -604,28 +604,32 @@ INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20;
INSERT INTO ft2 (c1,c2,c3) INSERT INTO ft2 (c1,c2,c3)
VALUES (1101,201,'aaa'), (1102,202,'bbb'), (1103,203,'ccc') RETURNING *; VALUES (1101,201,'aaa'), (1102,202,'bbb'), (1103,203,'ccc') RETURNING *;
INSERT INTO ft2 (c1,c2,c3) VALUES (1104,204,'ddd'), (1105,205,'eee'); INSERT INTO ft2 (c1,c2,c3) VALUES (1104,204,'ddd'), (1105,205,'eee');
EXPLAIN (verbose, costs off)
UPDATE ft2 SET c2 = c2 + 300, c3 = c3 || '_update3' WHERE c1 % 10 = 3; -- can be pushed down
UPDATE ft2 SET c2 = c2 + 300, c3 = c3 || '_update3' WHERE c1 % 10 = 3; UPDATE ft2 SET c2 = c2 + 300, c3 = c3 || '_update3' WHERE c1 % 10 = 3;
EXPLAIN (verbose, costs off)
UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *; -- can be pushed down
UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *; UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *;
EXPLAIN (verbose, costs off) EXPLAIN (verbose, costs off)
UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT
FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9; FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9; -- can't be pushed down
UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT
FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9; FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9;
EXPLAIN (verbose, costs off) EXPLAIN (verbose, costs off)
DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4; DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4; -- can be pushed down
DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4; DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4;
EXPLAIN (verbose, costs off) EXPLAIN (verbose, costs off)
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; -- can't be pushed down
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; SELECT c1,c2,c3,c4 FROM ft2 ORDER BY c1;
EXPLAIN (verbose, costs off) 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;
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) 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; -- can be pushed down
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) EXPLAIN (verbose, costs off)
DELETE FROM ft2 WHERE c1 = 9999 RETURNING tableoid::regclass; DELETE FROM ft2 WHERE c1 = 9999 RETURNING tableoid::regclass; -- can be pushed down
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 -- Test that trigger on remote table works as expected
@ -954,6 +958,90 @@ UPDATE rem1 SET f2 = 'testo';
-- Test returning a system attribute -- Test returning a system attribute
INSERT INTO rem1(f2) VALUES ('test') RETURNING ctid; INSERT INTO rem1(f2) VALUES ('test') RETURNING ctid;
-- cleanup
DROP TRIGGER trig_row_before ON rem1;
DROP TRIGGER trig_row_after ON rem1;
DROP TRIGGER trig_local_before ON loc1;
-- Test direct foreign table modification functionality
-- Test with statement-level triggers
CREATE TRIGGER trig_stmt_before
BEFORE DELETE OR INSERT OR UPDATE ON rem1
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
EXPLAIN (verbose, costs off)
UPDATE rem1 set f2 = ''; -- can be pushed down
EXPLAIN (verbose, costs off)
DELETE FROM rem1; -- can be pushed down
DROP TRIGGER trig_stmt_before ON rem1;
CREATE TRIGGER trig_stmt_after
AFTER DELETE OR INSERT OR UPDATE ON rem1
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
EXPLAIN (verbose, costs off)
UPDATE rem1 set f2 = ''; -- can be pushed down
EXPLAIN (verbose, costs off)
DELETE FROM rem1; -- can be pushed down
DROP TRIGGER trig_stmt_after ON rem1;
-- Test with row-level ON INSERT triggers
CREATE TRIGGER trig_row_before_insert
BEFORE INSERT ON rem1
FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
EXPLAIN (verbose, costs off)
UPDATE rem1 set f2 = ''; -- can be pushed down
EXPLAIN (verbose, costs off)
DELETE FROM rem1; -- can be pushed down
DROP TRIGGER trig_row_before_insert ON rem1;
CREATE TRIGGER trig_row_after_insert
AFTER INSERT ON rem1
FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
EXPLAIN (verbose, costs off)
UPDATE rem1 set f2 = ''; -- can be pushed down
EXPLAIN (verbose, costs off)
DELETE FROM rem1; -- can be pushed down
DROP TRIGGER trig_row_after_insert ON rem1;
-- Test with row-level ON UPDATE triggers
CREATE TRIGGER trig_row_before_update
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
EXPLAIN (verbose, costs off)
DELETE FROM rem1; -- can be pushed down
DROP TRIGGER trig_row_before_update ON rem1;
CREATE TRIGGER trig_row_after_update
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
EXPLAIN (verbose, costs off)
DELETE FROM rem1; -- can be pushed down
DROP TRIGGER trig_row_after_update ON rem1;
-- Test with row-level ON DELETE triggers
CREATE TRIGGER trig_row_before_delete
BEFORE DELETE ON rem1
FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
EXPLAIN (verbose, costs off)
UPDATE rem1 set f2 = ''; -- can be pushed down
EXPLAIN (verbose, costs off)
DELETE FROM rem1; -- can't be pushed down
DROP TRIGGER trig_row_before_delete ON rem1;
CREATE TRIGGER trig_row_after_delete
AFTER DELETE ON rem1
FOR EACH ROW EXECUTE PROCEDURE trigger_data(23,'skidoo');
EXPLAIN (verbose, costs off)
UPDATE rem1 set f2 = ''; -- can be pushed down
EXPLAIN (verbose, costs off)
DELETE FROM rem1; -- can't be pushed down
DROP TRIGGER trig_row_after_delete ON rem1;
-- =================================================================== -- ===================================================================
-- test inheritance features -- test inheritance features
-- =================================================================== -- ===================================================================
@ -1085,6 +1173,13 @@ fetch from c;
update bar set f2 = null where current of c; update bar set f2 = null where current of c;
rollback; rollback;
explain (verbose, costs off)
delete from foo where f1 < 5 returning *;
delete from foo where f1 < 5 returning *;
explain (verbose, costs off)
update bar set f2 = f2 + 100 returning *;
update bar set f2 = f2 + 100 returning *;
drop table foo cascade; drop table foo cascade;
drop table bar cascade; drop table bar cascade;
drop table loct1; drop table loct1;

View File

@ -698,6 +698,158 @@ IsForeignRelUpdatable (Relation rel);
updatability for display in the <literal>information_schema</> views.) updatability for display in the <literal>information_schema</> views.)
</para> </para>
<para>
Some inserts, updates, and deletes to foreign tables can be optimized
by implementing an alternative set of interfaces. The ordinary
interfaces for inserts, updates, and deletes fetch rows from the remote
server and then modify those rows one at a time. In some cases, this
row-by-row approach is necessary, but it can be inefficient. If it is
possible for the foreign server to determine which rows should be
modified without actually retrieving them, and if there are no local
triggers which would affect the operation, then it is possible to
arrange things so that the entire operation is performed on the remote
server. The interfaces described below make this possible.
</para>
<para>
<programlisting>
bool
PlanDirectModify (PlannerInfo *root,
ModifyTable *plan,
Index resultRelation,
int subplan_index);
</programlisting>
Decide whether it is safe to execute a direct modification
on the remote server. If so, return <literal>true</> after performing
planning actions needed for that. Otherwise, return <literal>false</>.
This optional function is called during query planning.
If this function succeeds, <function>BeginDirectModify</>,
<function>IterateDirectModify</> and <function>EndDirectModify</> will
be called at the execution stage, instead. Otherwise, the table
modification will be executed using the table-updating functions
described above.
The parameters are the same as for <function>PlanForeignModify</>.
</para>
<para>
To execute the direct modification on the remote server, this function
must rewrite the target subplan with a <structname>ForeignScan</> plan
node that executes the direct modification on the remote server. The
<structfield>operation</> field of the <structname>ForeignScan</> must
be set to the <literal>CmdType</> enumeration appropriately; that is,
<literal>CMD_UPDATE</> for <command>UPDATE</>,
<literal>CMD_INSERT</> for <command>INSERT</>, and
<literal>CMD_DELETE</> for <command>DELETE</>.
</para>
<para>
See <xref linkend="fdw-planning"> for additional information.
</para>
<para>
If the <function>PlanDirectModify</> pointer is set to
<literal>NULL</>, no attempts to execute a direct modification on the
remote server are taken.
</para>
<para>
<programlisting>
void
BeginDirectModify (ForeignScanState *node,
int eflags);
</programlisting>
Prepare to execute a direct modification on the remote server.
This is called during executor startup. It should perform any
initialization needed prior to the direct modification (that should be
done upon the first call to <function>IterateDirectModify</>).
The <structname>ForeignScanState</> node has already been created, but
its <structfield>fdw_state</> field is still NULL. Information about
the table to modify is accessible through the
<structname>ForeignScanState</> node (in particular, from the underlying
<structname>ForeignScan</> plan node, which contains any FDW-private
information provided by <function>PlanDirectModify</>).
<literal>eflags</> contains flag bits describing the executor's
operating mode for this plan node.
</para>
<para>
Note that when <literal>(eflags &amp; EXEC_FLAG_EXPLAIN_ONLY)</> is
true, this function should not perform any externally-visible actions;
it should only do the minimum required to make the node state valid
for <function>ExplainDirectModify</> and <function>EndDirectModify</>.
</para>
<para>
If the <function>BeginDirectModify</> pointer is set to
<literal>NULL</>, no attempts to execute a direct modification on the
remote server are taken.
</para>
<para>
<programlisting>
TupleTableSlot *
IterateDirectModify (ForeignScanState *node);
</programlisting>
When the <command>INSERT</>, <command>UPDATE</> or <command>DELETE</>
query doesn't have a <literal>RETURNING</> clause, just return NULL
after a direct modification on the remote server.
When the query has the clause, fetch one result containing the data
needed for the <literal>RETURNING</> calculation, returning it in a
tuple table slot (the node's <structfield>ScanTupleSlot</> should be
used for this purpose). The data that was actually inserted, updated
or deleted must be stored in the
<literal>es_result_relation_info-&gt;ri_projectReturning-&gt;pi_exprContext-&gt;ecxt_scantuple</>
of the node's <structname>EState</>.
Return NULL if no more rows are available.
Note that this is called in a short-lived memory context that will be
reset between invocations. Create a memory context in
<function>BeginDirectModify</> if you need longer-lived storage, or use
the <structfield>es_query_cxt</> of the node's <structname>EState</>.
</para>
<para>
The rows returned must match the <structfield>fdw_scan_tlist</> target
list if one was supplied, otherwise they must match the row type of the
foreign table being updated. If you choose to optimize away fetching
columns that are not needed for the <literal>RETURNING</> calculation,
you should insert nulls in those column positions, or else generate a
<structfield>fdw_scan_tlist</> list with those columns omitted.
</para>
<para>
Whether the query has the clause or not, the query's reported row count
must be incremented by the FDW itself. When the query doesn't have the
clause, the FDW must also increment the row count for the
<structname>ForeignScanState</> node in the <command>EXPLAIN ANALYZE</>
case.
</para>
<para>
If the <function>IterateDirectModify</> pointer is set to
<literal>NULL</>, no attempts to execute a direct modification on the
remote server are taken.
</para>
<para>
<programlisting>
void
EndDirectModify (ForeignScanState *node);
</programlisting>
Clean up following a direc modification on the remote server. It is
normally not important to release palloc'd memory, but for example open
files and connections to the remote server should be cleaned up.
</para>
<para>
If the <function>EndDirectModify</> pointer is set to
<literal>NULL</>, no attempts to execute a direct modification on the
remote server are taken.
</para>
</sect2> </sect2>
<sect2 id="fdw-callbacks-row-locking"> <sect2 id="fdw-callbacks-row-locking">
@ -889,6 +1041,29 @@ ExplainForeignModify (ModifyTableState *mtstate,
<command>EXPLAIN</>. <command>EXPLAIN</>.
</para> </para>
<para>
<programlisting>
void
ExplainDirectModify (ForeignScanState *node,
ExplainState *es);
</programlisting>
Print additional <command>EXPLAIN</> output for a direct modification
on the remote server.
This function can call <function>ExplainPropertyText</> and
related functions to add fields to the <command>EXPLAIN</> output.
The flag fields in <literal>es</> can be used to determine what to
print, and the state of the <structname>ForeignScanState</> node
can be inspected to provide run-time statistics in the <command>EXPLAIN
ANALYZE</> case.
</para>
<para>
If the <function>ExplainDirectModify</> pointer is set to
<literal>NULL</>, no additional information is printed during
<command>EXPLAIN</>.
</para>
</sect2> </sect2>
<sect2 id="fdw-callbacks-analyze"> <sect2 id="fdw-callbacks-analyze">
@ -1194,7 +1369,7 @@ GetForeignServerByName(const char *name, bool missing_ok);
The FDW callback functions <function>GetForeignRelSize</>, The FDW callback functions <function>GetForeignRelSize</>,
<function>GetForeignPaths</>, <function>GetForeignPlan</>, <function>GetForeignPaths</>, <function>GetForeignPlan</>,
<function>PlanForeignModify</>, <function>GetForeignJoinPaths</>, <function>PlanForeignModify</>, <function>GetForeignJoinPaths</>,
and <function>GetForeignUpperPaths</> <function>GetForeignUpperPaths</>, and <function>PlanDirectModify</>
must fit into the workings of the <productname>PostgreSQL</> planner. must fit into the workings of the <productname>PostgreSQL</> planner.
Here are some notes about what they must do. Here are some notes about what they must do.
</para> </para>
@ -1391,7 +1566,8 @@ GetForeignServerByName(const char *name, bool missing_ok);
<para> <para>
When planning an <command>UPDATE</> or <command>DELETE</>, When planning an <command>UPDATE</> or <command>DELETE</>,
<function>PlanForeignModify</> can look up the <structname>RelOptInfo</> <function>PlanForeignModify</> and <function>PlanDirectModify</>
can look up the <structname>RelOptInfo</>
struct for the foreign table and make use of the struct for the foreign table and make use of the
<literal>baserel-&gt;fdw_private</> data previously created by the <literal>baserel-&gt;fdw_private</> data previously created by the
scan-planning functions. However, in <command>INSERT</> the target scan-planning functions. However, in <command>INSERT</> the target

View File

@ -484,6 +484,15 @@
extension that's listed in the foreign server's <literal>extensions</> extension that's listed in the foreign server's <literal>extensions</>
option. Operators and functions in such clauses must option. Operators and functions in such clauses must
be <literal>IMMUTABLE</> as well. be <literal>IMMUTABLE</> as well.
For an <command>UPDATE</> or <command>DELETE</> query,
<filename>postgres_fdw</> attempts to optimize the query execution by
sending the whole query to the remote server if there are no query
<literal>WHERE</> clauses that cannot be sent to the remote server,
no local joins for the query, and no row-level local <literal>BEFORE</> or
<literal>AFTER</> triggers on the target table. In <command>UPDATE</>,
expressions to assign to target columns must use only built-in data types,
<literal>IMMUTABLE</> operators, or <literal>IMMUTABLE</> functions,
to reduce the risk of misexecution of the query.
</para> </para>
<para> <para>

View File

@ -900,7 +900,29 @@ ExplainNode(PlanState *planstate, List *ancestors,
pname = sname = "WorkTable Scan"; pname = sname = "WorkTable Scan";
break; break;
case T_ForeignScan: case T_ForeignScan:
pname = sname = "Foreign Scan"; sname = "Foreign Scan";
switch (((ForeignScan *) plan)->operation)
{
case CMD_SELECT:
pname = "Foreign Scan";
operation = "Select";
break;
case CMD_INSERT:
pname = "Foreign Insert";
operation = "Insert";
break;
case CMD_UPDATE:
pname = "Foreign Update";
operation = "Update";
break;
case CMD_DELETE:
pname = "Foreign Delete";
operation = "Delete";
break;
default:
pname = "???";
break;
}
break; break;
case T_CustomScan: case T_CustomScan:
sname = "Custom Scan"; sname = "Custom Scan";
@ -1648,6 +1670,19 @@ show_plan_tlist(PlanState *planstate, List *ancestors, ExplainState *es)
return; return;
if (IsA(plan, RecursiveUnion)) if (IsA(plan, RecursiveUnion))
return; return;
/*
* Likewise for ForeignScan that executes a direct INSERT/UPDATE/DELETE
*
* Note: the tlist for a ForeignScan that executes a direct INSERT/UPDATE
* might contain subplan output expressions that are confusing in this
* context. The tlist for a ForeignScan that executes a direct UPDATE/
* DELETE always contains "junk" target columns to identify the exact row
* to update or delete, which would be confusing in this context. So, we
* suppress it in all the cases.
*/
if (IsA(plan, ForeignScan) &&
((ForeignScan *) plan)->operation != CMD_SELECT)
return;
/* Set up deparsing context */ /* Set up deparsing context */
context = set_deparse_context_planstate(es->deparse_cxt, context = set_deparse_context_planstate(es->deparse_cxt,
@ -2236,8 +2271,16 @@ show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es)
FdwRoutine *fdwroutine = fsstate->fdwroutine; FdwRoutine *fdwroutine = fsstate->fdwroutine;
/* Let the FDW emit whatever fields it wants */ /* Let the FDW emit whatever fields it wants */
if (fdwroutine->ExplainForeignScan != NULL) if (((ForeignScan *) fsstate->ss.ps.plan)->operation != CMD_SELECT)
fdwroutine->ExplainForeignScan(fsstate, es); {
if (fdwroutine->ExplainDirectModify != NULL)
fdwroutine->ExplainDirectModify(fsstate, es);
}
else
{
if (fdwroutine->ExplainForeignScan != NULL)
fdwroutine->ExplainForeignScan(fsstate, es);
}
} }
/* /*
@ -2623,8 +2666,10 @@ show_modifytable_info(ModifyTableState *mtstate, List *ancestors,
} }
} }
/* Give FDW a chance */ /* Give FDW a chance if needed */
if (fdwroutine && fdwroutine->ExplainForeignModify != NULL) if (!resultRelInfo->ri_usesFdwDirectModify &&
fdwroutine != NULL &&
fdwroutine->ExplainForeignModify != NULL)
{ {
List *fdw_private = (List *) list_nth(node->fdwPrivLists, j); List *fdw_private = (List *) list_nth(node->fdwPrivLists, j);

View File

@ -1245,6 +1245,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
else else
resultRelInfo->ri_FdwRoutine = NULL; resultRelInfo->ri_FdwRoutine = NULL;
resultRelInfo->ri_FdwState = NULL; resultRelInfo->ri_FdwState = NULL;
resultRelInfo->ri_usesFdwDirectModify = false;
resultRelInfo->ri_ConstraintExprs = NULL; resultRelInfo->ri_ConstraintExprs = NULL;
resultRelInfo->ri_junkFilter = NULL; resultRelInfo->ri_junkFilter = NULL;
resultRelInfo->ri_projectReturning = NULL; resultRelInfo->ri_projectReturning = NULL;

View File

@ -48,7 +48,10 @@ ForeignNext(ForeignScanState *node)
/* Call the Iterate function in short-lived context */ /* Call the Iterate function in short-lived context */
oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory); oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
slot = node->fdwroutine->IterateForeignScan(node); if (plan->operation != CMD_SELECT)
slot = node->fdwroutine->IterateDirectModify(node);
else
slot = node->fdwroutine->IterateForeignScan(node);
MemoryContextSwitchTo(oldcontext); MemoryContextSwitchTo(oldcontext);
/* /*
@ -226,7 +229,10 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
/* /*
* Tell the FDW to initialize the scan. * Tell the FDW to initialize the scan.
*/ */
fdwroutine->BeginForeignScan(scanstate, eflags); if (node->operation != CMD_SELECT)
fdwroutine->BeginDirectModify(scanstate, eflags);
else
fdwroutine->BeginForeignScan(scanstate, eflags);
return scanstate; return scanstate;
} }
@ -240,8 +246,13 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
void void
ExecEndForeignScan(ForeignScanState *node) ExecEndForeignScan(ForeignScanState *node)
{ {
ForeignScan *plan = (ForeignScan *) node->ss.ps.plan;
/* Let the FDW shut down */ /* Let the FDW shut down */
node->fdwroutine->EndForeignScan(node); if (plan->operation != CMD_SELECT)
node->fdwroutine->EndDirectModify(node);
else
node->fdwroutine->EndForeignScan(node);
/* Shut down any outer plan. */ /* Shut down any outer plan. */
if (outerPlanState(node)) if (outerPlanState(node))

View File

@ -138,13 +138,17 @@ ExecCheckPlanOutput(Relation resultRel, List *targetList)
* tupleSlot: slot holding tuple actually inserted/updated/deleted * tupleSlot: slot holding tuple actually inserted/updated/deleted
* planSlot: slot holding tuple returned by top subplan node * planSlot: slot holding tuple returned by top subplan node
* *
* Note: If tupleSlot is NULL, the FDW should have already provided econtext's
* scan tuple.
*
* Returns a slot holding the result tuple * Returns a slot holding the result tuple
*/ */
static TupleTableSlot * static TupleTableSlot *
ExecProcessReturning(ProjectionInfo *projectReturning, ExecProcessReturning(ResultRelInfo *resultRelInfo,
TupleTableSlot *tupleSlot, TupleTableSlot *tupleSlot,
TupleTableSlot *planSlot) TupleTableSlot *planSlot)
{ {
ProjectionInfo *projectReturning = resultRelInfo->ri_projectReturning;
ExprContext *econtext = projectReturning->pi_exprContext; ExprContext *econtext = projectReturning->pi_exprContext;
/* /*
@ -154,7 +158,20 @@ ExecProcessReturning(ProjectionInfo *projectReturning,
ResetExprContext(econtext); ResetExprContext(econtext);
/* Make tuple and any needed join variables available to ExecProject */ /* Make tuple and any needed join variables available to ExecProject */
econtext->ecxt_scantuple = tupleSlot; if (tupleSlot)
econtext->ecxt_scantuple = tupleSlot;
else
{
HeapTuple tuple;
/*
* RETURNING expressions might reference the tableoid column, so
* initialize t_tableOid before evaluating them.
*/
Assert(!TupIsNull(econtext->ecxt_scantuple));
tuple = ExecMaterializeSlot(econtext->ecxt_scantuple);
tuple->t_tableOid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
}
econtext->ecxt_outertuple = planSlot; econtext->ecxt_outertuple = planSlot;
/* Compute the RETURNING expressions */ /* Compute the RETURNING expressions */
@ -496,8 +513,7 @@ ExecInsert(ModifyTableState *mtstate,
/* Process RETURNING if present */ /* Process RETURNING if present */
if (resultRelInfo->ri_projectReturning) if (resultRelInfo->ri_projectReturning)
return ExecProcessReturning(resultRelInfo->ri_projectReturning, return ExecProcessReturning(resultRelInfo, slot, planSlot);
slot, planSlot);
return NULL; return NULL;
} }
@ -738,8 +754,7 @@ ldelete:;
ExecStoreTuple(&deltuple, slot, InvalidBuffer, false); ExecStoreTuple(&deltuple, slot, InvalidBuffer, false);
} }
rslot = ExecProcessReturning(resultRelInfo->ri_projectReturning, rslot = ExecProcessReturning(resultRelInfo, slot, planSlot);
slot, planSlot);
/* /*
* Before releasing the target tuple again, make sure rslot has a * Before releasing the target tuple again, make sure rslot has a
@ -1024,8 +1039,7 @@ lreplace:;
/* Process RETURNING if present */ /* Process RETURNING if present */
if (resultRelInfo->ri_projectReturning) if (resultRelInfo->ri_projectReturning)
return ExecProcessReturning(resultRelInfo->ri_projectReturning, return ExecProcessReturning(resultRelInfo, slot, planSlot);
slot, planSlot);
return NULL; return NULL;
} }
@ -1380,6 +1394,26 @@ ExecModifyTable(ModifyTableState *node)
break; break;
} }
/*
* If resultRelInfo->ri_usesFdwDirectModify is true, all we need to do
* here is compute the RETURNING expressions.
*/
if (resultRelInfo->ri_usesFdwDirectModify)
{
Assert(resultRelInfo->ri_projectReturning);
/*
* A scan slot containing the data that was actually inserted,
* updated or deleted has already been made available to
* ExecProcessReturning by IterateDirectModify, so no need to
* provide it here.
*/
slot = ExecProcessReturning(resultRelInfo, NULL, planSlot);
estate->es_result_relation_info = saved_resultRelInfo;
return slot;
}
EvalPlanQualSetSlot(&node->mt_epqstate, planSlot); EvalPlanQualSetSlot(&node->mt_epqstate, planSlot);
slot = planSlot; slot = planSlot;
@ -1559,6 +1593,10 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
{ {
subplan = (Plan *) lfirst(l); subplan = (Plan *) lfirst(l);
/* Initialize the usesFdwDirectModify flag */
resultRelInfo->ri_usesFdwDirectModify = bms_is_member(i,
node->fdwDirectModifyPlans);
/* /*
* Verify result relation is a valid target for the current operation * Verify result relation is a valid target for the current operation
*/ */
@ -1583,7 +1621,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags); mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
/* Also let FDWs init themselves for foreign-table result rels */ /* Also let FDWs init themselves for foreign-table result rels */
if (resultRelInfo->ri_FdwRoutine != NULL && if (!resultRelInfo->ri_usesFdwDirectModify &&
resultRelInfo->ri_FdwRoutine != NULL &&
resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL) resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
{ {
List *fdw_private = (List *) list_nth(node->fdwPrivLists, i); List *fdw_private = (List *) list_nth(node->fdwPrivLists, i);
@ -1910,7 +1949,8 @@ ExecEndModifyTable(ModifyTableState *node)
{ {
ResultRelInfo *resultRelInfo = node->resultRelInfo + i; ResultRelInfo *resultRelInfo = node->resultRelInfo + i;
if (resultRelInfo->ri_FdwRoutine != NULL && if (!resultRelInfo->ri_usesFdwDirectModify &&
resultRelInfo->ri_FdwRoutine != NULL &&
resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL) resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
resultRelInfo->ri_FdwRoutine->EndForeignModify(node->ps.state, resultRelInfo->ri_FdwRoutine->EndForeignModify(node->ps.state,
resultRelInfo); resultRelInfo);

View File

@ -188,6 +188,7 @@ _copyModifyTable(const ModifyTable *from)
COPY_NODE_FIELD(withCheckOptionLists); COPY_NODE_FIELD(withCheckOptionLists);
COPY_NODE_FIELD(returningLists); COPY_NODE_FIELD(returningLists);
COPY_NODE_FIELD(fdwPrivLists); COPY_NODE_FIELD(fdwPrivLists);
COPY_BITMAPSET_FIELD(fdwDirectModifyPlans);
COPY_NODE_FIELD(rowMarks); COPY_NODE_FIELD(rowMarks);
COPY_SCALAR_FIELD(epqParam); COPY_SCALAR_FIELD(epqParam);
COPY_SCALAR_FIELD(onConflictAction); COPY_SCALAR_FIELD(onConflictAction);
@ -648,6 +649,7 @@ _copyForeignScan(const ForeignScan *from)
/* /*
* copy remainder of node * copy remainder of node
*/ */
COPY_SCALAR_FIELD(operation);
COPY_SCALAR_FIELD(fs_server); COPY_SCALAR_FIELD(fs_server);
COPY_NODE_FIELD(fdw_exprs); COPY_NODE_FIELD(fdw_exprs);
COPY_NODE_FIELD(fdw_private); COPY_NODE_FIELD(fdw_private);

View File

@ -356,6 +356,7 @@ _outModifyTable(StringInfo str, const ModifyTable *node)
WRITE_NODE_FIELD(withCheckOptionLists); WRITE_NODE_FIELD(withCheckOptionLists);
WRITE_NODE_FIELD(returningLists); WRITE_NODE_FIELD(returningLists);
WRITE_NODE_FIELD(fdwPrivLists); WRITE_NODE_FIELD(fdwPrivLists);
WRITE_BITMAPSET_FIELD(fdwDirectModifyPlans);
WRITE_NODE_FIELD(rowMarks); WRITE_NODE_FIELD(rowMarks);
WRITE_INT_FIELD(epqParam); WRITE_INT_FIELD(epqParam);
WRITE_ENUM_FIELD(onConflictAction, OnConflictAction); WRITE_ENUM_FIELD(onConflictAction, OnConflictAction);
@ -608,6 +609,7 @@ _outForeignScan(StringInfo str, const ForeignScan *node)
_outScanInfo(str, (const Scan *) node); _outScanInfo(str, (const Scan *) node);
WRITE_ENUM_FIELD(operation, CmdType);
WRITE_OID_FIELD(fs_server); WRITE_OID_FIELD(fs_server);
WRITE_NODE_FIELD(fdw_exprs); WRITE_NODE_FIELD(fdw_exprs);
WRITE_NODE_FIELD(fdw_private); WRITE_NODE_FIELD(fdw_private);

View File

@ -1481,6 +1481,7 @@ _readModifyTable(void)
READ_NODE_FIELD(withCheckOptionLists); READ_NODE_FIELD(withCheckOptionLists);
READ_NODE_FIELD(returningLists); READ_NODE_FIELD(returningLists);
READ_NODE_FIELD(fdwPrivLists); READ_NODE_FIELD(fdwPrivLists);
READ_BITMAPSET_FIELD(fdwDirectModifyPlans);
READ_NODE_FIELD(rowMarks); READ_NODE_FIELD(rowMarks);
READ_INT_FIELD(epqParam); READ_INT_FIELD(epqParam);
READ_ENUM_FIELD(onConflictAction, OnConflictAction); READ_ENUM_FIELD(onConflictAction, OnConflictAction);

View File

@ -4906,6 +4906,7 @@ make_foreignscan(List *qptlist,
plan->lefttree = outer_plan; plan->lefttree = outer_plan;
plan->righttree = NULL; plan->righttree = NULL;
node->scan.scanrelid = scanrelid; node->scan.scanrelid = scanrelid;
node->operation = CMD_SELECT;
/* fs_server will be filled in by create_foreignscan_plan */ /* fs_server will be filled in by create_foreignscan_plan */
node->fs_server = InvalidOid; node->fs_server = InvalidOid;
node->fdw_exprs = fdw_exprs; node->fdw_exprs = fdw_exprs;
@ -6021,6 +6022,7 @@ make_modifytable(PlannerInfo *root,
{ {
ModifyTable *node = makeNode(ModifyTable); ModifyTable *node = makeNode(ModifyTable);
List *fdw_private_list; List *fdw_private_list;
Bitmapset *direct_modify_plans;
ListCell *lc; ListCell *lc;
int i; int i;
@ -6078,12 +6080,14 @@ make_modifytable(PlannerInfo *root,
* construct private plan data, and accumulate it all into a list. * construct private plan data, and accumulate it all into a list.
*/ */
fdw_private_list = NIL; fdw_private_list = NIL;
direct_modify_plans = NULL;
i = 0; i = 0;
foreach(lc, resultRelations) foreach(lc, resultRelations)
{ {
Index rti = lfirst_int(lc); Index rti = lfirst_int(lc);
FdwRoutine *fdwroutine; FdwRoutine *fdwroutine;
List *fdw_private; List *fdw_private;
bool direct_modify;
/* /*
* If possible, we want to get the FdwRoutine from our RelOptInfo for * If possible, we want to get the FdwRoutine from our RelOptInfo for
@ -6110,7 +6114,23 @@ make_modifytable(PlannerInfo *root,
fdwroutine = NULL; fdwroutine = NULL;
} }
/*
* If the target foreign table has any row-level triggers, we can't
* modify the foreign table directly.
*/
direct_modify = false;
if (fdwroutine != NULL && if (fdwroutine != NULL &&
fdwroutine->PlanDirectModify != NULL &&
fdwroutine->BeginDirectModify != NULL &&
fdwroutine->IterateDirectModify != NULL &&
fdwroutine->EndDirectModify != NULL &&
!has_row_triggers(root, rti, operation))
direct_modify = fdwroutine->PlanDirectModify(root, node, rti, i);
if (direct_modify)
direct_modify_plans = bms_add_member(direct_modify_plans, i);
if (!direct_modify &&
fdwroutine != NULL &&
fdwroutine->PlanForeignModify != NULL) fdwroutine->PlanForeignModify != NULL)
fdw_private = fdwroutine->PlanForeignModify(root, node, rti, i); fdw_private = fdwroutine->PlanForeignModify(root, node, rti, i);
else else
@ -6119,6 +6139,7 @@ make_modifytable(PlannerInfo *root,
i++; i++;
} }
node->fdwPrivLists = fdw_private_list; node->fdwPrivLists = fdw_private_list;
node->fdwDirectModifyPlans = direct_modify_plans;
return node; return node;
} }

View File

@ -1540,3 +1540,50 @@ has_unique_index(RelOptInfo *rel, AttrNumber attno)
} }
return false; return false;
} }
/*
* has_row_triggers
*
* Detect whether the specified relation has any row-level triggers for event.
*/
bool
has_row_triggers(PlannerInfo *root, Index rti, CmdType event)
{
RangeTblEntry *rte = planner_rt_fetch(rti, root);
Relation relation;
TriggerDesc *trigDesc;
bool result = false;
/* Assume we already have adequate lock */
relation = heap_open(rte->relid, NoLock);
trigDesc = relation->trigdesc;
switch (event)
{
case CMD_INSERT:
if (trigDesc &&
(trigDesc->trig_insert_after_row ||
trigDesc->trig_insert_before_row))
result = true;
break;
case CMD_UPDATE:
if (trigDesc &&
(trigDesc->trig_update_after_row ||
trigDesc->trig_update_before_row))
result = true;
break;
case CMD_DELETE:
if (trigDesc &&
(trigDesc->trig_delete_after_row ||
trigDesc->trig_delete_before_row))
result = true;
break;
default:
elog(ERROR, "unrecognized CmdType: %d", (int) event);
break;
}
heap_close(relation, NoLock);
return result;
}

View File

@ -97,6 +97,18 @@ typedef void (*EndForeignModify_function) (EState *estate,
typedef int (*IsForeignRelUpdatable_function) (Relation rel); typedef int (*IsForeignRelUpdatable_function) (Relation rel);
typedef bool (*PlanDirectModify_function) (PlannerInfo *root,
ModifyTable *plan,
Index resultRelation,
int subplan_index);
typedef void (*BeginDirectModify_function) (ForeignScanState *node,
int eflags);
typedef TupleTableSlot *(*IterateDirectModify_function) (ForeignScanState *node);
typedef void (*EndDirectModify_function) (ForeignScanState *node);
typedef RowMarkType (*GetForeignRowMarkType_function) (RangeTblEntry *rte, typedef RowMarkType (*GetForeignRowMarkType_function) (RangeTblEntry *rte,
LockClauseStrength strength); LockClauseStrength strength);
@ -114,6 +126,9 @@ typedef void (*ExplainForeignModify_function) (ModifyTableState *mtstate,
int subplan_index, int subplan_index,
struct ExplainState *es); struct ExplainState *es);
typedef void (*ExplainDirectModify_function) (ForeignScanState *node,
struct ExplainState *es);
typedef int (*AcquireSampleRowsFunc) (Relation relation, int elevel, typedef int (*AcquireSampleRowsFunc) (Relation relation, int elevel,
HeapTuple *rows, int targrows, HeapTuple *rows, int targrows,
double *totalrows, double *totalrows,
@ -181,6 +196,10 @@ typedef struct FdwRoutine
ExecForeignDelete_function ExecForeignDelete; ExecForeignDelete_function ExecForeignDelete;
EndForeignModify_function EndForeignModify; EndForeignModify_function EndForeignModify;
IsForeignRelUpdatable_function IsForeignRelUpdatable; IsForeignRelUpdatable_function IsForeignRelUpdatable;
PlanDirectModify_function PlanDirectModify;
BeginDirectModify_function BeginDirectModify;
IterateDirectModify_function IterateDirectModify;
EndDirectModify_function EndDirectModify;
/* Functions for SELECT FOR UPDATE/SHARE row locking */ /* Functions for SELECT FOR UPDATE/SHARE row locking */
GetForeignRowMarkType_function GetForeignRowMarkType; GetForeignRowMarkType_function GetForeignRowMarkType;
@ -190,6 +209,7 @@ typedef struct FdwRoutine
/* Support functions for EXPLAIN */ /* Support functions for EXPLAIN */
ExplainForeignScan_function ExplainForeignScan; ExplainForeignScan_function ExplainForeignScan;
ExplainForeignModify_function ExplainForeignModify; ExplainForeignModify_function ExplainForeignModify;
ExplainDirectModify_function ExplainDirectModify;
/* Support functions for ANALYZE */ /* Support functions for ANALYZE */
AnalyzeForeignTable_function AnalyzeForeignTable; AnalyzeForeignTable_function AnalyzeForeignTable;

View File

@ -311,6 +311,7 @@ typedef struct JunkFilter
* TrigInstrument optional runtime measurements for triggers * TrigInstrument optional runtime measurements for triggers
* FdwRoutine FDW callback functions, if foreign table * FdwRoutine FDW callback functions, if foreign table
* FdwState available to save private state of FDW * FdwState available to save private state of FDW
* usesFdwDirectModify true when modifying foreign table directly
* WithCheckOptions list of WithCheckOption's to be checked * WithCheckOptions list of WithCheckOption's to be checked
* WithCheckOptionExprs list of WithCheckOption expr states * WithCheckOptionExprs list of WithCheckOption expr states
* ConstraintExprs array of constraint-checking expr states * ConstraintExprs array of constraint-checking expr states
@ -334,6 +335,7 @@ typedef struct ResultRelInfo
Instrumentation *ri_TrigInstrument; Instrumentation *ri_TrigInstrument;
struct FdwRoutine *ri_FdwRoutine; struct FdwRoutine *ri_FdwRoutine;
void *ri_FdwState; void *ri_FdwState;
bool ri_usesFdwDirectModify;
List *ri_WithCheckOptions; List *ri_WithCheckOptions;
List *ri_WithCheckOptionExprs; List *ri_WithCheckOptionExprs;
List **ri_ConstraintExprs; List **ri_ConstraintExprs;

View File

@ -134,16 +134,19 @@ list_length(const List *l)
#define list_make2(x1,x2) lcons(x1, list_make1(x2)) #define list_make2(x1,x2) lcons(x1, list_make1(x2))
#define list_make3(x1,x2,x3) lcons(x1, list_make2(x2, x3)) #define list_make3(x1,x2,x3) lcons(x1, list_make2(x2, x3))
#define list_make4(x1,x2,x3,x4) lcons(x1, list_make3(x2, x3, x4)) #define list_make4(x1,x2,x3,x4) lcons(x1, list_make3(x2, x3, x4))
#define list_make5(x1,x2,x3,x4,x5) lcons(x1, list_make4(x2, x3, x4, x5))
#define list_make1_int(x1) lcons_int(x1, NIL) #define list_make1_int(x1) lcons_int(x1, NIL)
#define list_make2_int(x1,x2) lcons_int(x1, list_make1_int(x2)) #define list_make2_int(x1,x2) lcons_int(x1, list_make1_int(x2))
#define list_make3_int(x1,x2,x3) lcons_int(x1, list_make2_int(x2, x3)) #define list_make3_int(x1,x2,x3) lcons_int(x1, list_make2_int(x2, x3))
#define list_make4_int(x1,x2,x3,x4) lcons_int(x1, list_make3_int(x2, x3, x4)) #define list_make4_int(x1,x2,x3,x4) lcons_int(x1, list_make3_int(x2, x3, x4))
#define list_make5_int(x1,x2,x3,x4,x5) lcons_int(x1, list_make4_int(x2, x3, x4, x5))
#define list_make1_oid(x1) lcons_oid(x1, NIL) #define list_make1_oid(x1) lcons_oid(x1, NIL)
#define list_make2_oid(x1,x2) lcons_oid(x1, list_make1_oid(x2)) #define list_make2_oid(x1,x2) lcons_oid(x1, list_make1_oid(x2))
#define list_make3_oid(x1,x2,x3) lcons_oid(x1, list_make2_oid(x2, x3)) #define list_make3_oid(x1,x2,x3) lcons_oid(x1, list_make2_oid(x2, x3))
#define list_make4_oid(x1,x2,x3,x4) lcons_oid(x1, list_make3_oid(x2, x3, x4)) #define list_make4_oid(x1,x2,x3,x4) lcons_oid(x1, list_make3_oid(x2, x3, x4))
#define list_make5_oid(x1,x2,x3,x4,x5) lcons_oid(x1, list_make4_oid(x2, x3, x4, x5))
/* /*
* foreach - * foreach -

View File

@ -189,6 +189,7 @@ typedef struct ModifyTable
List *withCheckOptionLists; /* per-target-table WCO lists */ List *withCheckOptionLists; /* per-target-table WCO lists */
List *returningLists; /* per-target-table RETURNING tlists */ List *returningLists; /* per-target-table RETURNING tlists */
List *fdwPrivLists; /* per-target-table FDW private data lists */ List *fdwPrivLists; /* per-target-table FDW private data lists */
Bitmapset *fdwDirectModifyPlans; /* indices of FDW DM plans */
List *rowMarks; /* PlanRowMarks (non-locking only) */ List *rowMarks; /* PlanRowMarks (non-locking only) */
int epqParam; /* ID of Param for EvalPlanQual re-eval */ int epqParam; /* ID of Param for EvalPlanQual re-eval */
OnConflictAction onConflictAction; /* ON CONFLICT action */ OnConflictAction onConflictAction; /* ON CONFLICT action */
@ -531,6 +532,7 @@ typedef struct WorkTableScan
typedef struct ForeignScan typedef struct ForeignScan
{ {
Scan scan; Scan scan;
CmdType operation; /* SELECT/INSERT/UPDATE/DELETE */
Oid fs_server; /* OID of foreign server */ Oid fs_server; /* OID of foreign server */
List *fdw_exprs; /* expressions that FDW may evaluate */ List *fdw_exprs; /* expressions that FDW may evaluate */
List *fdw_private; /* private data for FDW */ List *fdw_private; /* private data for FDW */

View File

@ -55,4 +55,6 @@ extern Selectivity join_selectivity(PlannerInfo *root,
JoinType jointype, JoinType jointype,
SpecialJoinInfo *sjinfo); SpecialJoinInfo *sjinfo);
extern bool has_row_triggers(PlannerInfo *root, Index rti, CmdType event);
#endif /* PLANCAT_H */ #endif /* PLANCAT_H */