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:
parent
3422feccca
commit
0bf3ae88af
21 changed files with 1515 additions and 125 deletions
|
@ -1314,6 +1314,69 @@ deparseUpdateSql(StringInfo buf, PlannerInfo *root,
|
|||
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
|
||||
*
|
||||
|
@ -1336,6 +1399,43 @@ deparseDeleteSql(StringInfo buf, PlannerInfo *root,
|
|||
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.
|
||||
*/
|
||||
|
|
|
@ -2259,7 +2259,26 @@ INSERT INTO ft2 (c1,c2,c3)
|
|||
(3 rows)
|
||||
|
||||
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;
|
||||
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 *;
|
||||
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)
|
||||
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
|
||||
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
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
|
||||
FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9;
|
||||
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
|
||||
----------------------------------------------------------------------------------------
|
||||
--------------------------------------------------------------------------------------------
|
||||
Delete on public.ft2
|
||||
Output: c1, c4
|
||||
Remote SQL: DELETE FROM "S 1"."T 1" WHERE ctid = $1 RETURNING "C 1", c4
|
||||
-> Foreign Scan on public.ft2
|
||||
Output: ctid
|
||||
Remote SQL: SELECT ctid FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 5)) FOR UPDATE
|
||||
(6 rows)
|
||||
-> Foreign Delete on public.ft2
|
||||
Remote SQL: DELETE FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 5)) RETURNING "C 1", c4
|
||||
(4 rows)
|
||||
|
||||
DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4;
|
||||
c1 | c4
|
||||
|
@ -2514,7 +2531,7 @@ DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4;
|
|||
(103 rows)
|
||||
|
||||
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
|
||||
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
||||
Delete on public.ft2
|
||||
|
@ -3379,16 +3396,14 @@ INSERT INTO ft2 (c1,c2,c3) VALUES (9999,999,'foo') RETURNING tableoid::regclass;
|
|||
(1 row)
|
||||
|
||||
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
|
||||
-------------------------------------------------------------------------------------------------------------------
|
||||
------------------------------------------------------------------------------------
|
||||
Update on public.ft2
|
||||
Output: (tableoid)::regclass
|
||||
Remote SQL: UPDATE "S 1"."T 1" SET c3 = $2 WHERE ctid = $1
|
||||
-> Foreign Scan on public.ft2
|
||||
Output: c1, c2, NULL::integer, 'bar'::text, c4, c5, c6, c7, c8, ctid
|
||||
Remote SQL: SELECT "C 1", c2, c4, c5, c6, c7, c8, ctid FROM "S 1"."T 1" WHERE (("C 1" = 9999)) FOR UPDATE
|
||||
(6 rows)
|
||||
-> Foreign Update on public.ft2
|
||||
Remote SQL: UPDATE "S 1"."T 1" SET c3 = 'bar'::text WHERE (("C 1" = 9999))
|
||||
(4 rows)
|
||||
|
||||
UPDATE ft2 SET c3 = 'bar' WHERE c1 = 9999 RETURNING tableoid::regclass;
|
||||
tableoid
|
||||
|
@ -3397,16 +3412,14 @@ UPDATE ft2 SET c3 = 'bar' WHERE c1 = 9999 RETURNING tableoid::regclass;
|
|||
(1 row)
|
||||
|
||||
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
|
||||
------------------------------------------------------------------------------------
|
||||
--------------------------------------------------------------------
|
||||
Delete on public.ft2
|
||||
Output: (tableoid)::regclass
|
||||
Remote SQL: DELETE FROM "S 1"."T 1" WHERE ctid = $1
|
||||
-> Foreign Scan on public.ft2
|
||||
Output: ctid
|
||||
Remote SQL: SELECT ctid FROM "S 1"."T 1" WHERE (("C 1" = 9999)) FOR UPDATE
|
||||
(6 rows)
|
||||
-> Foreign Delete on public.ft2
|
||||
Remote SQL: DELETE FROM "S 1"."T 1" WHERE (("C 1" = 9999))
|
||||
(4 rows)
|
||||
|
||||
DELETE FROM ft2 WHERE c1 = 9999 RETURNING tableoid::regclass;
|
||||
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
|
||||
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).
|
||||
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
|
||||
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
|
||||
c2 | count
|
||||
|
@ -3719,7 +3732,7 @@ savepoint s3;
|
|||
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"
|
||||
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;
|
||||
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
|
||||
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
|
||||
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).
|
||||
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;
|
||||
-- But inconsistent check constraints provide inconsistent results
|
||||
ALTER FOREIGN TABLE ft1 ADD CONSTRAINT ft1_c2negative CHECK (c2 < 0);
|
||||
|
@ -4332,6 +4345,199 @@ NOTICE: NEW: (13,"test triggered !")
|
|||
(0,27)
|
||||
(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
|
||||
-- ===================================================================
|
||||
|
@ -4801,6 +5007,56 @@ fetch from c;
|
|||
update bar set f2 = null where current of c;
|
||||
ERROR: WHERE CURRENT OF is not supported for this table type
|
||||
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;
|
||||
NOTICE: drop cascades to foreign table foo2
|
||||
drop table bar cascade;
|
||||
|
|
|
@ -61,6 +61,8 @@ enum FdwScanPrivateIndex
|
|||
{
|
||||
/* SQL statement to execute remotely (as a String node) */
|
||||
FdwScanPrivateSelectSql,
|
||||
/* List of restriction clauses that can be executed remotely */
|
||||
FdwScanPrivateRemoteConds,
|
||||
/* Integer list of attribute numbers retrieved by the SELECT */
|
||||
FdwScanPrivateRetrievedAttrs,
|
||||
/* Integer representing the desired fetch_size */
|
||||
|
@ -97,6 +99,27 @@ enum FdwModifyPrivateIndex
|
|||
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.
|
||||
*/
|
||||
|
@ -163,6 +186,36 @@ typedef struct PgFdwModifyState
|
|||
MemoryContext temp_cxt; /* context for per-tuple temporary data */
|
||||
} 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.
|
||||
*/
|
||||
|
@ -263,6 +316,13 @@ static TupleTableSlot *postgresExecForeignDelete(EState *estate,
|
|||
static void postgresEndForeignModify(EState *estate,
|
||||
ResultRelInfo *resultRelInfo);
|
||||
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,
|
||||
ExplainState *es);
|
||||
static void postgresExplainForeignModify(ModifyTableState *mtstate,
|
||||
|
@ -270,6 +330,8 @@ static void postgresExplainForeignModify(ModifyTableState *mtstate,
|
|||
List *fdw_private,
|
||||
int subplan_index,
|
||||
ExplainState *es);
|
||||
static void postgresExplainDirectModify(ForeignScanState *node,
|
||||
ExplainState *es);
|
||||
static bool postgresAnalyzeForeignTable(Relation relation,
|
||||
AcquireSampleRowsFunc *func,
|
||||
BlockNumber *totalpages);
|
||||
|
@ -311,6 +373,18 @@ static const char **convert_prep_stmt_params(PgFdwModifyState *fmstate,
|
|||
TupleTableSlot *slot);
|
||||
static void store_returning_result(PgFdwModifyState *fmstate,
|
||||
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,
|
||||
HeapTuple *rows, int targrows,
|
||||
double *totalrows,
|
||||
|
@ -362,12 +436,17 @@ postgres_fdw_handler(PG_FUNCTION_ARGS)
|
|||
routine->ExecForeignDelete = postgresExecForeignDelete;
|
||||
routine->EndForeignModify = postgresEndForeignModify;
|
||||
routine->IsForeignRelUpdatable = postgresIsForeignRelUpdatable;
|
||||
routine->PlanDirectModify = postgresPlanDirectModify;
|
||||
routine->BeginDirectModify = postgresBeginDirectModify;
|
||||
routine->IterateDirectModify = postgresIterateDirectModify;
|
||||
routine->EndDirectModify = postgresEndDirectModify;
|
||||
|
||||
/* Function for EvalPlanQual rechecks */
|
||||
routine->RecheckForeignScan = postgresRecheckForeignScan;
|
||||
/* Support functions for EXPLAIN */
|
||||
routine->ExplainForeignScan = postgresExplainForeignScan;
|
||||
routine->ExplainForeignModify = postgresExplainForeignModify;
|
||||
routine->ExplainDirectModify = postgresExplainDirectModify;
|
||||
|
||||
/* Support functions for ANALYZE */
|
||||
routine->AnalyzeForeignTable = postgresAnalyzeForeignTable;
|
||||
|
@ -1122,7 +1201,8 @@ postgresGetForeignPlan(PlannerInfo *root,
|
|||
* 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),
|
||||
fdw_private = list_make5(makeString(sql.data),
|
||||
remote_conds,
|
||||
retrieved_attrs,
|
||||
makeInteger(fpinfo->fetch_size),
|
||||
makeInteger(foreignrel->umid));
|
||||
|
@ -1159,8 +1239,6 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
|
|||
PgFdwScanState *fsstate;
|
||||
UserMapping *user;
|
||||
int numParams;
|
||||
int i;
|
||||
ListCell *lc;
|
||||
|
||||
/*
|
||||
* 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);
|
||||
|
||||
/* 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);
|
||||
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)
|
||||
fsstate->param_values = (const char **) palloc0(numParams * sizeof(char *));
|
||||
else
|
||||
fsstate->param_values = NULL;
|
||||
prepare_query_params((PlanState *) node,
|
||||
fsplan->fdw_exprs,
|
||||
numParams,
|
||||
&fsstate->param_flinfo,
|
||||
&fsstate->param_exprs,
|
||||
&fsstate->param_values);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -1447,13 +1501,6 @@ postgresAddForeignUpdateTargets(Query *parsetree,
|
|||
/*
|
||||
* postgresPlanForeignModify
|
||||
* 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 *
|
||||
postgresPlanForeignModify(PlannerInfo *root,
|
||||
|
@ -1991,6 +2038,314 @@ postgresRecheckForeignScan(ForeignScanState *node, TupleTableSlot *slot)
|
|||
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, ¶ms_list,
|
||||
returningList, &retrieved_attrs);
|
||||
break;
|
||||
case CMD_DELETE:
|
||||
deparseDirectDeleteSql(&sql, root, resultRelation, rel,
|
||||
remote_conds, ¶ms_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
|
||||
* 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
|
||||
|
@ -2419,38 +2793,14 @@ create_cursor(ForeignScanState *node)
|
|||
*/
|
||||
if (numParams > 0)
|
||||
{
|
||||
int nestlevel;
|
||||
MemoryContext oldcontext;
|
||||
int i;
|
||||
ListCell *lc;
|
||||
|
||||
oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
|
||||
|
||||
nestlevel = set_transmission_modes();
|
||||
|
||||
i = 0;
|
||||
foreach(lc, fsstate->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)
|
||||
values[i] = NULL;
|
||||
else
|
||||
values[i] = OutputFunctionCall(&fsstate->param_flinfo[i],
|
||||
expr_value);
|
||||
i++;
|
||||
}
|
||||
|
||||
reset_transmission_modes(nestlevel);
|
||||
process_query_params(econtext,
|
||||
fsstate->param_flinfo,
|
||||
fsstate->param_exprs,
|
||||
values);
|
||||
|
||||
MemoryContextSwitchTo(oldcontext);
|
||||
}
|
||||
|
@ -2770,6 +3120,197 @@ store_returning_result(PgFdwModifyState *fmstate,
|
|||
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(¶m_flinfo[i], expr_value);
|
||||
i++;
|
||||
}
|
||||
|
||||
reset_transmission_modes(nestlevel);
|
||||
}
|
||||
|
||||
/*
|
||||
* postgresAnalyzeForeignTable
|
||||
* Test whether analyzing this foreign table is supported
|
||||
|
|
|
@ -130,10 +130,24 @@ extern void deparseUpdateSql(StringInfo buf, PlannerInfo *root,
|
|||
Index rtindex, Relation rel,
|
||||
List *targetAttrs, List *returningList,
|
||||
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,
|
||||
Index rtindex, Relation rel,
|
||||
List *returningList,
|
||||
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 deparseAnalyzeSql(StringInfo buf, Relation rel,
|
||||
List **retrieved_attrs);
|
||||
|
|
|
@ -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)
|
||||
VALUES (1101,201,'aaa'), (1102,202,'bbb'), (1103,203,'ccc') RETURNING *;
|
||||
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;
|
||||
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 *;
|
||||
EXPLAIN (verbose, costs off)
|
||||
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
|
||||
FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9;
|
||||
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;
|
||||
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;
|
||||
SELECT c1,c2,c3,c4 FROM ft2 ORDER BY c1;
|
||||
EXPLAIN (verbose, costs off)
|
||||
INSERT INTO ft2 (c1,c2,c3) VALUES (9999,999,'foo') RETURNING tableoid::regclass;
|
||||
INSERT INTO ft2 (c1,c2,c3) VALUES (9999,999,'foo') RETURNING tableoid::regclass;
|
||||
EXPLAIN (verbose, costs off)
|
||||
UPDATE ft2 SET c3 = 'bar' WHERE c1 = 9999 RETURNING tableoid::regclass;
|
||||
UPDATE ft2 SET c3 = 'bar' WHERE c1 = 9999 RETURNING tableoid::regclass; -- can be pushed down
|
||||
UPDATE ft2 SET c3 = 'bar' WHERE c1 = 9999 RETURNING tableoid::regclass;
|
||||
EXPLAIN (verbose, costs off)
|
||||
DELETE FROM ft2 WHERE c1 = 9999 RETURNING tableoid::regclass;
|
||||
DELETE FROM ft2 WHERE c1 = 9999 RETURNING tableoid::regclass; -- can be pushed down
|
||||
DELETE FROM ft2 WHERE c1 = 9999 RETURNING tableoid::regclass;
|
||||
|
||||
-- Test that trigger on remote table works as expected
|
||||
|
@ -954,6 +958,90 @@ UPDATE rem1 SET f2 = 'testo';
|
|||
-- Test returning a system attribute
|
||||
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
|
||||
-- ===================================================================
|
||||
|
@ -1085,6 +1173,13 @@ fetch from c;
|
|||
update bar set f2 = null where current of c;
|
||||
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 bar cascade;
|
||||
drop table loct1;
|
||||
|
|
|
@ -698,6 +698,158 @@ IsForeignRelUpdatable (Relation rel);
|
|||
updatability for display in the <literal>information_schema</> views.)
|
||||
</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 & 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->ri_projectReturning->pi_exprContext->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 id="fdw-callbacks-row-locking">
|
||||
|
@ -889,6 +1041,29 @@ ExplainForeignModify (ModifyTableState *mtstate,
|
|||
<command>EXPLAIN</>.
|
||||
</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 id="fdw-callbacks-analyze">
|
||||
|
@ -1194,7 +1369,7 @@ GetForeignServerByName(const char *name, bool missing_ok);
|
|||
The FDW callback functions <function>GetForeignRelSize</>,
|
||||
<function>GetForeignPaths</>, <function>GetForeignPlan</>,
|
||||
<function>PlanForeignModify</>, <function>GetForeignJoinPaths</>,
|
||||
and <function>GetForeignUpperPaths</>
|
||||
<function>GetForeignUpperPaths</>, and <function>PlanDirectModify</>
|
||||
must fit into the workings of the <productname>PostgreSQL</> planner.
|
||||
Here are some notes about what they must do.
|
||||
</para>
|
||||
|
@ -1391,7 +1566,8 @@ GetForeignServerByName(const char *name, bool missing_ok);
|
|||
|
||||
<para>
|
||||
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
|
||||
<literal>baserel->fdw_private</> data previously created by the
|
||||
scan-planning functions. However, in <command>INSERT</> the target
|
||||
|
|
|
@ -484,6 +484,15 @@
|
|||
extension that's listed in the foreign server's <literal>extensions</>
|
||||
option. Operators and functions in such clauses must
|
||||
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>
|
||||
|
|
|
@ -900,7 +900,29 @@ ExplainNode(PlanState *planstate, List *ancestors,
|
|||
pname = sname = "WorkTable Scan";
|
||||
break;
|
||||
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;
|
||||
case T_CustomScan:
|
||||
sname = "Custom Scan";
|
||||
|
@ -1648,6 +1670,19 @@ show_plan_tlist(PlanState *planstate, List *ancestors, ExplainState *es)
|
|||
return;
|
||||
if (IsA(plan, RecursiveUnion))
|
||||
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 */
|
||||
context = set_deparse_context_planstate(es->deparse_cxt,
|
||||
|
@ -2236,8 +2271,16 @@ show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es)
|
|||
FdwRoutine *fdwroutine = fsstate->fdwroutine;
|
||||
|
||||
/* Let the FDW emit whatever fields it wants */
|
||||
if (((ForeignScan *) fsstate->ss.ps.plan)->operation != CMD_SELECT)
|
||||
{
|
||||
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 */
|
||||
if (fdwroutine && fdwroutine->ExplainForeignModify != NULL)
|
||||
/* Give FDW a chance if needed */
|
||||
if (!resultRelInfo->ri_usesFdwDirectModify &&
|
||||
fdwroutine != NULL &&
|
||||
fdwroutine->ExplainForeignModify != NULL)
|
||||
{
|
||||
List *fdw_private = (List *) list_nth(node->fdwPrivLists, j);
|
||||
|
||||
|
|
|
@ -1245,6 +1245,7 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
|
|||
else
|
||||
resultRelInfo->ri_FdwRoutine = NULL;
|
||||
resultRelInfo->ri_FdwState = NULL;
|
||||
resultRelInfo->ri_usesFdwDirectModify = false;
|
||||
resultRelInfo->ri_ConstraintExprs = NULL;
|
||||
resultRelInfo->ri_junkFilter = NULL;
|
||||
resultRelInfo->ri_projectReturning = NULL;
|
||||
|
|
|
@ -48,6 +48,9 @@ ForeignNext(ForeignScanState *node)
|
|||
|
||||
/* Call the Iterate function in short-lived context */
|
||||
oldcontext = MemoryContextSwitchTo(econtext->ecxt_per_tuple_memory);
|
||||
if (plan->operation != CMD_SELECT)
|
||||
slot = node->fdwroutine->IterateDirectModify(node);
|
||||
else
|
||||
slot = node->fdwroutine->IterateForeignScan(node);
|
||||
MemoryContextSwitchTo(oldcontext);
|
||||
|
||||
|
@ -226,6 +229,9 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
|
|||
/*
|
||||
* Tell the FDW to initialize the scan.
|
||||
*/
|
||||
if (node->operation != CMD_SELECT)
|
||||
fdwroutine->BeginDirectModify(scanstate, eflags);
|
||||
else
|
||||
fdwroutine->BeginForeignScan(scanstate, eflags);
|
||||
|
||||
return scanstate;
|
||||
|
@ -240,7 +246,12 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
|
|||
void
|
||||
ExecEndForeignScan(ForeignScanState *node)
|
||||
{
|
||||
ForeignScan *plan = (ForeignScan *) node->ss.ps.plan;
|
||||
|
||||
/* Let the FDW shut down */
|
||||
if (plan->operation != CMD_SELECT)
|
||||
node->fdwroutine->EndDirectModify(node);
|
||||
else
|
||||
node->fdwroutine->EndForeignScan(node);
|
||||
|
||||
/* Shut down any outer plan. */
|
||||
|
|
|
@ -138,13 +138,17 @@ ExecCheckPlanOutput(Relation resultRel, List *targetList)
|
|||
* tupleSlot: slot holding tuple actually inserted/updated/deleted
|
||||
* 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
|
||||
*/
|
||||
static TupleTableSlot *
|
||||
ExecProcessReturning(ProjectionInfo *projectReturning,
|
||||
ExecProcessReturning(ResultRelInfo *resultRelInfo,
|
||||
TupleTableSlot *tupleSlot,
|
||||
TupleTableSlot *planSlot)
|
||||
{
|
||||
ProjectionInfo *projectReturning = resultRelInfo->ri_projectReturning;
|
||||
ExprContext *econtext = projectReturning->pi_exprContext;
|
||||
|
||||
/*
|
||||
|
@ -154,7 +158,20 @@ ExecProcessReturning(ProjectionInfo *projectReturning,
|
|||
ResetExprContext(econtext);
|
||||
|
||||
/* Make tuple and any needed join variables available to ExecProject */
|
||||
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;
|
||||
|
||||
/* Compute the RETURNING expressions */
|
||||
|
@ -496,8 +513,7 @@ ExecInsert(ModifyTableState *mtstate,
|
|||
|
||||
/* Process RETURNING if present */
|
||||
if (resultRelInfo->ri_projectReturning)
|
||||
return ExecProcessReturning(resultRelInfo->ri_projectReturning,
|
||||
slot, planSlot);
|
||||
return ExecProcessReturning(resultRelInfo, slot, planSlot);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
@ -738,8 +754,7 @@ ldelete:;
|
|||
ExecStoreTuple(&deltuple, slot, InvalidBuffer, false);
|
||||
}
|
||||
|
||||
rslot = ExecProcessReturning(resultRelInfo->ri_projectReturning,
|
||||
slot, planSlot);
|
||||
rslot = ExecProcessReturning(resultRelInfo, slot, planSlot);
|
||||
|
||||
/*
|
||||
* Before releasing the target tuple again, make sure rslot has a
|
||||
|
@ -1024,8 +1039,7 @@ lreplace:;
|
|||
|
||||
/* Process RETURNING if present */
|
||||
if (resultRelInfo->ri_projectReturning)
|
||||
return ExecProcessReturning(resultRelInfo->ri_projectReturning,
|
||||
slot, planSlot);
|
||||
return ExecProcessReturning(resultRelInfo, slot, planSlot);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
@ -1380,6 +1394,26 @@ ExecModifyTable(ModifyTableState *node)
|
|||
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);
|
||||
slot = planSlot;
|
||||
|
||||
|
@ -1559,6 +1593,10 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
|
|||
{
|
||||
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
|
||||
*/
|
||||
|
@ -1583,7 +1621,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
|
|||
mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
|
||||
|
||||
/* 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)
|
||||
{
|
||||
List *fdw_private = (List *) list_nth(node->fdwPrivLists, i);
|
||||
|
@ -1910,7 +1949,8 @@ ExecEndModifyTable(ModifyTableState *node)
|
|||
{
|
||||
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(node->ps.state,
|
||||
resultRelInfo);
|
||||
|
|
|
@ -188,6 +188,7 @@ _copyModifyTable(const ModifyTable *from)
|
|||
COPY_NODE_FIELD(withCheckOptionLists);
|
||||
COPY_NODE_FIELD(returningLists);
|
||||
COPY_NODE_FIELD(fdwPrivLists);
|
||||
COPY_BITMAPSET_FIELD(fdwDirectModifyPlans);
|
||||
COPY_NODE_FIELD(rowMarks);
|
||||
COPY_SCALAR_FIELD(epqParam);
|
||||
COPY_SCALAR_FIELD(onConflictAction);
|
||||
|
@ -648,6 +649,7 @@ _copyForeignScan(const ForeignScan *from)
|
|||
/*
|
||||
* copy remainder of node
|
||||
*/
|
||||
COPY_SCALAR_FIELD(operation);
|
||||
COPY_SCALAR_FIELD(fs_server);
|
||||
COPY_NODE_FIELD(fdw_exprs);
|
||||
COPY_NODE_FIELD(fdw_private);
|
||||
|
|
|
@ -356,6 +356,7 @@ _outModifyTable(StringInfo str, const ModifyTable *node)
|
|||
WRITE_NODE_FIELD(withCheckOptionLists);
|
||||
WRITE_NODE_FIELD(returningLists);
|
||||
WRITE_NODE_FIELD(fdwPrivLists);
|
||||
WRITE_BITMAPSET_FIELD(fdwDirectModifyPlans);
|
||||
WRITE_NODE_FIELD(rowMarks);
|
||||
WRITE_INT_FIELD(epqParam);
|
||||
WRITE_ENUM_FIELD(onConflictAction, OnConflictAction);
|
||||
|
@ -608,6 +609,7 @@ _outForeignScan(StringInfo str, const ForeignScan *node)
|
|||
|
||||
_outScanInfo(str, (const Scan *) node);
|
||||
|
||||
WRITE_ENUM_FIELD(operation, CmdType);
|
||||
WRITE_OID_FIELD(fs_server);
|
||||
WRITE_NODE_FIELD(fdw_exprs);
|
||||
WRITE_NODE_FIELD(fdw_private);
|
||||
|
|
|
@ -1481,6 +1481,7 @@ _readModifyTable(void)
|
|||
READ_NODE_FIELD(withCheckOptionLists);
|
||||
READ_NODE_FIELD(returningLists);
|
||||
READ_NODE_FIELD(fdwPrivLists);
|
||||
READ_BITMAPSET_FIELD(fdwDirectModifyPlans);
|
||||
READ_NODE_FIELD(rowMarks);
|
||||
READ_INT_FIELD(epqParam);
|
||||
READ_ENUM_FIELD(onConflictAction, OnConflictAction);
|
||||
|
|
|
@ -4906,6 +4906,7 @@ make_foreignscan(List *qptlist,
|
|||
plan->lefttree = outer_plan;
|
||||
plan->righttree = NULL;
|
||||
node->scan.scanrelid = scanrelid;
|
||||
node->operation = CMD_SELECT;
|
||||
/* fs_server will be filled in by create_foreignscan_plan */
|
||||
node->fs_server = InvalidOid;
|
||||
node->fdw_exprs = fdw_exprs;
|
||||
|
@ -6021,6 +6022,7 @@ make_modifytable(PlannerInfo *root,
|
|||
{
|
||||
ModifyTable *node = makeNode(ModifyTable);
|
||||
List *fdw_private_list;
|
||||
Bitmapset *direct_modify_plans;
|
||||
ListCell *lc;
|
||||
int i;
|
||||
|
||||
|
@ -6078,12 +6080,14 @@ make_modifytable(PlannerInfo *root,
|
|||
* construct private plan data, and accumulate it all into a list.
|
||||
*/
|
||||
fdw_private_list = NIL;
|
||||
direct_modify_plans = NULL;
|
||||
i = 0;
|
||||
foreach(lc, resultRelations)
|
||||
{
|
||||
Index rti = lfirst_int(lc);
|
||||
FdwRoutine *fdwroutine;
|
||||
List *fdw_private;
|
||||
bool direct_modify;
|
||||
|
||||
/*
|
||||
* If possible, we want to get the FdwRoutine from our RelOptInfo for
|
||||
|
@ -6110,7 +6114,23 @@ make_modifytable(PlannerInfo *root,
|
|||
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 &&
|
||||
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)
|
||||
fdw_private = fdwroutine->PlanForeignModify(root, node, rti, i);
|
||||
else
|
||||
|
@ -6119,6 +6139,7 @@ make_modifytable(PlannerInfo *root,
|
|||
i++;
|
||||
}
|
||||
node->fdwPrivLists = fdw_private_list;
|
||||
node->fdwDirectModifyPlans = direct_modify_plans;
|
||||
|
||||
return node;
|
||||
}
|
||||
|
|
|
@ -1540,3 +1540,50 @@ has_unique_index(RelOptInfo *rel, AttrNumber attno)
|
|||
}
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -97,6 +97,18 @@ typedef void (*EndForeignModify_function) (EState *estate,
|
|||
|
||||
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,
|
||||
LockClauseStrength strength);
|
||||
|
||||
|
@ -114,6 +126,9 @@ typedef void (*ExplainForeignModify_function) (ModifyTableState *mtstate,
|
|||
int subplan_index,
|
||||
struct ExplainState *es);
|
||||
|
||||
typedef void (*ExplainDirectModify_function) (ForeignScanState *node,
|
||||
struct ExplainState *es);
|
||||
|
||||
typedef int (*AcquireSampleRowsFunc) (Relation relation, int elevel,
|
||||
HeapTuple *rows, int targrows,
|
||||
double *totalrows,
|
||||
|
@ -181,6 +196,10 @@ typedef struct FdwRoutine
|
|||
ExecForeignDelete_function ExecForeignDelete;
|
||||
EndForeignModify_function EndForeignModify;
|
||||
IsForeignRelUpdatable_function IsForeignRelUpdatable;
|
||||
PlanDirectModify_function PlanDirectModify;
|
||||
BeginDirectModify_function BeginDirectModify;
|
||||
IterateDirectModify_function IterateDirectModify;
|
||||
EndDirectModify_function EndDirectModify;
|
||||
|
||||
/* Functions for SELECT FOR UPDATE/SHARE row locking */
|
||||
GetForeignRowMarkType_function GetForeignRowMarkType;
|
||||
|
@ -190,6 +209,7 @@ typedef struct FdwRoutine
|
|||
/* Support functions for EXPLAIN */
|
||||
ExplainForeignScan_function ExplainForeignScan;
|
||||
ExplainForeignModify_function ExplainForeignModify;
|
||||
ExplainDirectModify_function ExplainDirectModify;
|
||||
|
||||
/* Support functions for ANALYZE */
|
||||
AnalyzeForeignTable_function AnalyzeForeignTable;
|
||||
|
|
|
@ -311,6 +311,7 @@ typedef struct JunkFilter
|
|||
* TrigInstrument optional runtime measurements for triggers
|
||||
* FdwRoutine FDW callback functions, if foreign table
|
||||
* FdwState available to save private state of FDW
|
||||
* usesFdwDirectModify true when modifying foreign table directly
|
||||
* WithCheckOptions list of WithCheckOption's to be checked
|
||||
* WithCheckOptionExprs list of WithCheckOption expr states
|
||||
* ConstraintExprs array of constraint-checking expr states
|
||||
|
@ -334,6 +335,7 @@ typedef struct ResultRelInfo
|
|||
Instrumentation *ri_TrigInstrument;
|
||||
struct FdwRoutine *ri_FdwRoutine;
|
||||
void *ri_FdwState;
|
||||
bool ri_usesFdwDirectModify;
|
||||
List *ri_WithCheckOptions;
|
||||
List *ri_WithCheckOptionExprs;
|
||||
List **ri_ConstraintExprs;
|
||||
|
|
|
@ -134,16 +134,19 @@ list_length(const List *l)
|
|||
#define list_make2(x1,x2) lcons(x1, list_make1(x2))
|
||||
#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_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_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_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_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_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 -
|
||||
|
|
|
@ -189,6 +189,7 @@ typedef struct ModifyTable
|
|||
List *withCheckOptionLists; /* per-target-table WCO lists */
|
||||
List *returningLists; /* per-target-table RETURNING tlists */
|
||||
List *fdwPrivLists; /* per-target-table FDW private data lists */
|
||||
Bitmapset *fdwDirectModifyPlans; /* indices of FDW DM plans */
|
||||
List *rowMarks; /* PlanRowMarks (non-locking only) */
|
||||
int epqParam; /* ID of Param for EvalPlanQual re-eval */
|
||||
OnConflictAction onConflictAction; /* ON CONFLICT action */
|
||||
|
@ -531,6 +532,7 @@ typedef struct WorkTableScan
|
|||
typedef struct ForeignScan
|
||||
{
|
||||
Scan scan;
|
||||
CmdType operation; /* SELECT/INSERT/UPDATE/DELETE */
|
||||
Oid fs_server; /* OID of foreign server */
|
||||
List *fdw_exprs; /* expressions that FDW may evaluate */
|
||||
List *fdw_private; /* private data for FDW */
|
||||
|
|
|
@ -55,4 +55,6 @@ extern Selectivity join_selectivity(PlannerInfo *root,
|
|||
JoinType jointype,
|
||||
SpecialJoinInfo *sjinfo);
|
||||
|
||||
extern bool has_row_triggers(PlannerInfo *root, Index rti, CmdType event);
|
||||
|
||||
#endif /* PLANCAT_H */
|
||||
|
|
Loading…
Reference in a new issue