Commit e690b951 authored by Tom Lane's avatar Tom Lane

Avoid retrieving dummy NULL columns in postgres_fdw.

This should provide some marginal overall savings, since it surely takes
many more cycles for the remote server to deal with the NULL columns than
it takes for postgres_fdw not to emit them.  But really the reason is to
keep the emitted queries from looking quite so silly ...
parent 9cbc4b80
...@@ -106,10 +106,12 @@ static void deparseTargetList(StringInfo buf, ...@@ -106,10 +106,12 @@ static void deparseTargetList(StringInfo buf,
PlannerInfo *root, PlannerInfo *root,
Index rtindex, Index rtindex,
Relation rel, Relation rel,
Bitmapset *attrs_used); Bitmapset *attrs_used,
List **retrieved_attrs);
static void deparseReturningList(StringInfo buf, PlannerInfo *root, static void deparseReturningList(StringInfo buf, PlannerInfo *root,
Index rtindex, Relation rel, Index rtindex, Relation rel,
List *returningList); List *returningList,
List **retrieved_attrs);
static void deparseColumnRef(StringInfo buf, int varno, int varattno, static void deparseColumnRef(StringInfo buf, int varno, int varattno,
PlannerInfo *root); PlannerInfo *root);
static void deparseRelation(StringInfo buf, Relation rel); static void deparseRelation(StringInfo buf, Relation rel);
...@@ -652,12 +654,16 @@ is_builtin(Oid oid) ...@@ -652,12 +654,16 @@ is_builtin(Oid oid)
* Construct a simple SELECT statement that retrieves desired columns * Construct a simple SELECT statement that retrieves desired columns
* of the specified foreign table, and append it to "buf". The output * of the specified foreign table, and append it to "buf". The output
* contains just "SELECT ... FROM tablename". * contains just "SELECT ... FROM tablename".
*
* We also create an integer List of the columns being retrieved, which is
* returned to *retrieved_attrs.
*/ */
void void
deparseSelectSql(StringInfo buf, deparseSelectSql(StringInfo buf,
PlannerInfo *root, PlannerInfo *root,
RelOptInfo *baserel, RelOptInfo *baserel,
Bitmapset *attrs_used) Bitmapset *attrs_used,
List **retrieved_attrs)
{ {
RangeTblEntry *rte = planner_rt_fetch(baserel->relid, root); RangeTblEntry *rte = planner_rt_fetch(baserel->relid, root);
Relation rel; Relation rel;
...@@ -672,7 +678,8 @@ deparseSelectSql(StringInfo buf, ...@@ -672,7 +678,8 @@ deparseSelectSql(StringInfo buf,
* Construct SELECT list * Construct SELECT list
*/ */
appendStringInfoString(buf, "SELECT "); appendStringInfoString(buf, "SELECT ");
deparseTargetList(buf, root, baserel->relid, rel, attrs_used); deparseTargetList(buf, root, baserel->relid, rel, attrs_used,
retrieved_attrs);
/* /*
* Construct FROM clause * Construct FROM clause
...@@ -687,24 +694,24 @@ deparseSelectSql(StringInfo buf, ...@@ -687,24 +694,24 @@ deparseSelectSql(StringInfo buf,
* Emit a target list that retrieves the columns specified in attrs_used. * Emit a target list that retrieves the columns specified in attrs_used.
* This is used for both SELECT and RETURNING targetlists. * This is used for both SELECT and RETURNING targetlists.
* *
* We list attributes in order of the foreign table's columns, but replace * The tlist text is appended to buf, and we also create an integer List
* any attributes that need not be fetched with NULL constants. (We can't * of the columns being retrieved, which is returned to *retrieved_attrs.
* just omit such attributes, or we'll lose track of which columns are
* which at runtime.) Note however that any dropped columns are ignored.
* Also, if ctid needs to be retrieved, it's added at the end.
*/ */
static void static void
deparseTargetList(StringInfo buf, deparseTargetList(StringInfo buf,
PlannerInfo *root, PlannerInfo *root,
Index rtindex, Index rtindex,
Relation rel, Relation rel,
Bitmapset *attrs_used) Bitmapset *attrs_used,
List **retrieved_attrs)
{ {
TupleDesc tupdesc = RelationGetDescr(rel); TupleDesc tupdesc = RelationGetDescr(rel);
bool have_wholerow; bool have_wholerow;
bool first; bool first;
int i; int i;
*retrieved_attrs = NIL;
/* If there's a whole-row reference, we'll need all the columns. */ /* If there's a whole-row reference, we'll need all the columns. */
have_wholerow = bms_is_member(0 - FirstLowInvalidHeapAttributeNumber, have_wholerow = bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
attrs_used); attrs_used);
...@@ -718,16 +725,18 @@ deparseTargetList(StringInfo buf, ...@@ -718,16 +725,18 @@ deparseTargetList(StringInfo buf,
if (attr->attisdropped) if (attr->attisdropped)
continue; continue;
if (!first)
appendStringInfoString(buf, ", ");
first = false;
if (have_wholerow || if (have_wholerow ||
bms_is_member(i - FirstLowInvalidHeapAttributeNumber, bms_is_member(i - FirstLowInvalidHeapAttributeNumber,
attrs_used)) attrs_used))
{
if (!first)
appendStringInfoString(buf, ", ");
first = false;
deparseColumnRef(buf, rtindex, i, root); deparseColumnRef(buf, rtindex, i, root);
else
appendStringInfoString(buf, "NULL"); *retrieved_attrs = lappend_int(*retrieved_attrs, i);
}
} }
/* /*
...@@ -742,6 +751,9 @@ deparseTargetList(StringInfo buf, ...@@ -742,6 +751,9 @@ deparseTargetList(StringInfo buf,
first = false; first = false;
appendStringInfoString(buf, "ctid"); appendStringInfoString(buf, "ctid");
*retrieved_attrs = lappend_int(*retrieved_attrs,
SelfItemPointerAttributeNumber);
} }
/* Don't generate bad syntax if no undropped columns */ /* Don't generate bad syntax if no undropped columns */
...@@ -809,11 +821,16 @@ appendWhereClause(StringInfo buf, ...@@ -809,11 +821,16 @@ appendWhereClause(StringInfo buf,
/* /*
* deparse remote INSERT statement * deparse remote INSERT 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 void
deparseInsertSql(StringInfo buf, PlannerInfo *root, deparseInsertSql(StringInfo buf, PlannerInfo *root,
Index rtindex, Relation rel, Index rtindex, Relation rel,
List *targetAttrs, List *returningList) List *targetAttrs, List *returningList,
List **retrieved_attrs)
{ {
AttrNumber pindex; AttrNumber pindex;
bool first; bool first;
...@@ -858,16 +875,24 @@ deparseInsertSql(StringInfo buf, PlannerInfo *root, ...@@ -858,16 +875,24 @@ deparseInsertSql(StringInfo buf, PlannerInfo *root,
appendStringInfoString(buf, " DEFAULT VALUES"); appendStringInfoString(buf, " DEFAULT VALUES");
if (returningList) if (returningList)
deparseReturningList(buf, root, rtindex, rel, returningList); deparseReturningList(buf, root, rtindex, rel, returningList,
retrieved_attrs);
else
*retrieved_attrs = NIL;
} }
/* /*
* deparse remote UPDATE statement * 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 void
deparseUpdateSql(StringInfo buf, PlannerInfo *root, deparseUpdateSql(StringInfo buf, PlannerInfo *root,
Index rtindex, Relation rel, Index rtindex, Relation rel,
List *targetAttrs, List *returningList) List *targetAttrs, List *returningList,
List **retrieved_attrs)
{ {
AttrNumber pindex; AttrNumber pindex;
bool first; bool first;
...@@ -894,23 +919,34 @@ deparseUpdateSql(StringInfo buf, PlannerInfo *root, ...@@ -894,23 +919,34 @@ deparseUpdateSql(StringInfo buf, PlannerInfo *root,
appendStringInfoString(buf, " WHERE ctid = $1"); appendStringInfoString(buf, " WHERE ctid = $1");
if (returningList) if (returningList)
deparseReturningList(buf, root, rtindex, rel, returningList); deparseReturningList(buf, root, rtindex, rel, returningList,
retrieved_attrs);
else
*retrieved_attrs = NIL;
} }
/* /*
* deparse remote DELETE statement * 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 void
deparseDeleteSql(StringInfo buf, PlannerInfo *root, deparseDeleteSql(StringInfo buf, PlannerInfo *root,
Index rtindex, Relation rel, Index rtindex, Relation rel,
List *returningList) List *returningList,
List **retrieved_attrs)
{ {
appendStringInfoString(buf, "DELETE FROM "); appendStringInfoString(buf, "DELETE FROM ");
deparseRelation(buf, rel); deparseRelation(buf, rel);
appendStringInfoString(buf, " WHERE ctid = $1"); appendStringInfoString(buf, " WHERE ctid = $1");
if (returningList) if (returningList)
deparseReturningList(buf, root, rtindex, rel, returningList); deparseReturningList(buf, root, rtindex, rel, returningList,
retrieved_attrs);
else
*retrieved_attrs = NIL;
} }
/* /*
...@@ -919,7 +955,8 @@ deparseDeleteSql(StringInfo buf, PlannerInfo *root, ...@@ -919,7 +955,8 @@ deparseDeleteSql(StringInfo buf, PlannerInfo *root,
static void static void
deparseReturningList(StringInfo buf, PlannerInfo *root, deparseReturningList(StringInfo buf, PlannerInfo *root,
Index rtindex, Relation rel, Index rtindex, Relation rel,
List *returningList) List *returningList,
List **retrieved_attrs)
{ {
Bitmapset *attrs_used; Bitmapset *attrs_used;
...@@ -931,7 +968,8 @@ deparseReturningList(StringInfo buf, PlannerInfo *root, ...@@ -931,7 +968,8 @@ deparseReturningList(StringInfo buf, PlannerInfo *root,
&attrs_used); &attrs_used);
appendStringInfoString(buf, " RETURNING "); appendStringInfoString(buf, " RETURNING ");
deparseTargetList(buf, root, rtindex, rel, attrs_used); deparseTargetList(buf, root, rtindex, rel, attrs_used,
retrieved_attrs);
} }
/* /*
...@@ -959,10 +997,11 @@ deparseAnalyzeSizeSql(StringInfo buf, Relation rel) ...@@ -959,10 +997,11 @@ deparseAnalyzeSizeSql(StringInfo buf, Relation rel)
/* /*
* Construct SELECT statement to acquire sample rows of given relation. * Construct SELECT statement to acquire sample rows of given relation.
* *
* Note: command is appended to whatever might be in buf already. * SELECT command is appended to buf, and list of columns retrieved
* is returned to *retrieved_attrs.
*/ */
void void
deparseAnalyzeSql(StringInfo buf, Relation rel) deparseAnalyzeSql(StringInfo buf, Relation rel, List **retrieved_attrs)
{ {
Oid relid = RelationGetRelid(rel); Oid relid = RelationGetRelid(rel);
TupleDesc tupdesc = RelationGetDescr(rel); TupleDesc tupdesc = RelationGetDescr(rel);
...@@ -972,6 +1011,8 @@ deparseAnalyzeSql(StringInfo buf, Relation rel) ...@@ -972,6 +1011,8 @@ deparseAnalyzeSql(StringInfo buf, Relation rel)
ListCell *lc; ListCell *lc;
bool first = true; bool first = true;
*retrieved_attrs = NIL;
appendStringInfoString(buf, "SELECT "); appendStringInfoString(buf, "SELECT ");
for (i = 0; i < tupdesc->natts; i++) for (i = 0; i < tupdesc->natts; i++)
{ {
...@@ -999,6 +1040,8 @@ deparseAnalyzeSql(StringInfo buf, Relation rel) ...@@ -999,6 +1040,8 @@ deparseAnalyzeSql(StringInfo buf, Relation rel)
} }
appendStringInfoString(buf, quote_identifier(colname)); appendStringInfoString(buf, quote_identifier(colname));
*retrieved_attrs = lappend_int(*retrieved_attrs, i + 1);
} }
/* Don't generate bad syntax for zero-column relation. */ /* Don't generate bad syntax for zero-column relation. */
......
...@@ -473,16 +473,16 @@ SELECT * FROM ft2 a, ft2 b WHERE a.c1 = 47 AND b.c1 = a.c2; ...@@ -473,16 +473,16 @@ SELECT * FROM ft2 a, ft2 b WHERE a.c1 = 47 AND b.c1 = a.c2;
-- simple join -- simple join
PREPARE st1(int, int) AS SELECT t1.c3, t2.c3 FROM ft1 t1, ft2 t2 WHERE t1.c1 = $1 AND t2.c1 = $2; PREPARE st1(int, int) AS SELECT t1.c3, t2.c3 FROM ft1 t1, ft2 t2 WHERE t1.c1 = $1 AND t2.c1 = $2;
EXPLAIN (VERBOSE, COSTS false) EXECUTE st1(1, 2); EXPLAIN (VERBOSE, COSTS false) EXECUTE st1(1, 2);
QUERY PLAN QUERY PLAN
-------------------------------------------------------------------------------------------------------------- --------------------------------------------------------------------
Nested Loop Nested Loop
Output: t1.c3, t2.c3 Output: t1.c3, t2.c3
-> Foreign Scan on public.ft1 t1 -> Foreign Scan on public.ft1 t1
Output: t1.c3 Output: t1.c3
Remote SQL: SELECT NULL, NULL, c3, NULL, NULL, NULL, NULL, NULL FROM "S 1"."T 1" WHERE (("C 1" = 1)) Remote SQL: SELECT c3 FROM "S 1"."T 1" WHERE (("C 1" = 1))
-> Foreign Scan on public.ft2 t2 -> Foreign Scan on public.ft2 t2
Output: t2.c3 Output: t2.c3
Remote SQL: SELECT NULL, NULL, c3, NULL, NULL, NULL, NULL, NULL FROM "S 1"."T 1" WHERE (("C 1" = 2)) Remote SQL: SELECT c3 FROM "S 1"."T 1" WHERE (("C 1" = 2))
(8 rows) (8 rows)
EXECUTE st1(1, 1); EXECUTE st1(1, 1);
...@@ -500,8 +500,8 @@ EXECUTE st1(101, 101); ...@@ -500,8 +500,8 @@ EXECUTE st1(101, 101);
-- subquery using stable function (can't be sent to remote) -- subquery using stable function (can't be sent to remote)
PREPARE st2(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND date(c4) = '1970-01-17'::date) ORDER BY c1; PREPARE st2(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND date(c4) = '1970-01-17'::date) ORDER BY c1;
EXPLAIN (VERBOSE, COSTS false) EXECUTE st2(10, 20); EXPLAIN (VERBOSE, COSTS false) EXECUTE st2(10, 20);
QUERY PLAN QUERY PLAN
------------------------------------------------------------------------------------------------------------------------- ----------------------------------------------------------------------------------------------------------
Sort Sort
Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8 Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8
Sort Key: t1.c1 Sort Key: t1.c1
...@@ -516,7 +516,7 @@ EXPLAIN (VERBOSE, COSTS false) EXECUTE st2(10, 20); ...@@ -516,7 +516,7 @@ EXPLAIN (VERBOSE, COSTS false) EXECUTE st2(10, 20);
-> Foreign Scan on public.ft2 t2 -> Foreign Scan on public.ft2 t2
Output: t2.c3 Output: t2.c3
Filter: (date(t2.c4) = '01-17-1970'::date) Filter: (date(t2.c4) = '01-17-1970'::date)
Remote SQL: SELECT NULL, NULL, c3, c4, NULL, NULL, NULL, NULL FROM "S 1"."T 1" WHERE (("C 1" > 10)) Remote SQL: SELECT c3, c4 FROM "S 1"."T 1" WHERE (("C 1" > 10))
(15 rows) (15 rows)
EXECUTE st2(10, 20); EXECUTE st2(10, 20);
...@@ -534,8 +534,8 @@ EXECUTE st2(101, 121); ...@@ -534,8 +534,8 @@ EXECUTE st2(101, 121);
-- subquery using immutable function (can be sent to remote) -- subquery using immutable function (can be sent to remote)
PREPARE st3(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND date(c5) = '1970-01-17'::date) ORDER BY c1; PREPARE st3(int) AS SELECT * FROM ft1 t1 WHERE t1.c1 < $2 AND t1.c3 IN (SELECT c3 FROM ft2 t2 WHERE c1 > $1 AND date(c5) = '1970-01-17'::date) ORDER BY c1;
EXPLAIN (VERBOSE, COSTS false) EXECUTE st3(10, 20); EXPLAIN (VERBOSE, COSTS false) EXECUTE st3(10, 20);
QUERY PLAN QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------------------------- -----------------------------------------------------------------------------------------------------------------------
Sort Sort
Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8 Output: t1.c1, t1.c2, t1.c3, t1.c4, t1.c5, t1.c6, t1.c7, t1.c8
Sort Key: t1.c1 Sort Key: t1.c1
...@@ -549,7 +549,7 @@ EXPLAIN (VERBOSE, COSTS false) EXECUTE st3(10, 20); ...@@ -549,7 +549,7 @@ EXPLAIN (VERBOSE, COSTS false) EXECUTE st3(10, 20);
Output: t2.c3 Output: t2.c3
-> Foreign Scan on public.ft2 t2 -> Foreign Scan on public.ft2 t2
Output: t2.c3 Output: t2.c3
Remote SQL: SELECT NULL, NULL, c3, NULL, NULL, NULL, NULL, NULL FROM "S 1"."T 1" WHERE (("C 1" > 10)) AND ((date(c5) = '1970-01-17'::date)) Remote SQL: SELECT c3 FROM "S 1"."T 1" WHERE (("C 1" > 10)) AND ((date(c5) = '1970-01-17'::date))
(14 rows) (14 rows)
EXECUTE st3(10, 20); EXECUTE st3(10, 20);
...@@ -834,7 +834,7 @@ INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20; ...@@ -834,7 +834,7 @@ INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20;
Output: ((ft2_1.c1 + 1000)), ((ft2_1.c2 + 100)), ((ft2_1.c3 || ft2_1.c3)) Output: ((ft2_1.c1 + 1000)), ((ft2_1.c2 + 100)), ((ft2_1.c3 || ft2_1.c3))
-> Foreign Scan on public.ft2 ft2_1 -> Foreign Scan on public.ft2 ft2_1
Output: (ft2_1.c1 + 1000), (ft2_1.c2 + 100), (ft2_1.c3 || ft2_1.c3) Output: (ft2_1.c1 + 1000), (ft2_1.c2 + 100), (ft2_1.c3 || ft2_1.c3)
Remote SQL: SELECT "C 1", c2, c3, NULL, NULL, NULL, NULL, NULL FROM "S 1"."T 1" Remote SQL: SELECT "C 1", c2, c3 FROM "S 1"."T 1"
(9 rows) (9 rows)
INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20; INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20;
...@@ -968,7 +968,7 @@ UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT ...@@ -968,7 +968,7 @@ UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT
Hash Cond: (ft2.c2 = ft1.c1) Hash Cond: (ft2.c2 = ft1.c1)
-> Foreign Scan on public.ft2 -> Foreign Scan on public.ft2
Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c8, ft2.ctid Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c8, ft2.ctid
Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, NULL, c8, ctid FROM "S 1"."T 1" FOR UPDATE Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c8, ctid FROM "S 1"."T 1" FOR UPDATE
-> Hash -> Hash
Output: ft1.*, ft1.c1 Output: ft1.*, ft1.c1
-> Foreign Scan on public.ft1 -> Foreign Scan on public.ft1
...@@ -978,112 +978,124 @@ UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT ...@@ -978,112 +978,124 @@ 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;
DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING *; EXPLAIN (verbose, costs off)
c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8 DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4;
------+-----+------------+------------------------------+--------------------------+----+------------+----- QUERY PLAN
5 | 5 | 00005 | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5 | 5 | foo ----------------------------------------------------------------------------------------
15 | 5 | 00015 | Fri Jan 16 00:00:00 1970 PST | Fri Jan 16 00:00:00 1970 | 5 | 5 | foo Delete on public.ft2
25 | 5 | 00025 | Mon Jan 26 00:00:00 1970 PST | Mon Jan 26 00:00:00 1970 | 5 | 5 | foo Output: c1, c4
35 | 5 | 00035 | Thu Feb 05 00:00:00 1970 PST | Thu Feb 05 00:00:00 1970 | 5 | 5 | foo Remote SQL: DELETE FROM "S 1"."T 1" WHERE ctid = $1 RETURNING "C 1", c4
45 | 5 | 00045 | Sun Feb 15 00:00:00 1970 PST | Sun Feb 15 00:00:00 1970 | 5 | 5 | foo -> Foreign Scan on public.ft2
55 | 5 | 00055 | Wed Feb 25 00:00:00 1970 PST | Wed Feb 25 00:00:00 1970 | 5 | 5 | foo Output: ctid
65 | 5 | 00065 | Sat Mar 07 00:00:00 1970 PST | Sat Mar 07 00:00:00 1970 | 5 | 5 | foo Remote SQL: SELECT ctid FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 5)) FOR UPDATE
75 | 5 | 00075 | Tue Mar 17 00:00:00 1970 PST | Tue Mar 17 00:00:00 1970 | 5 | 5 | foo (6 rows)
85 | 5 | 00085 | Fri Mar 27 00:00:00 1970 PST | Fri Mar 27 00:00:00 1970 | 5 | 5 | foo
95 | 5 | 00095 | Mon Apr 06 00:00:00 1970 PST | Mon Apr 06 00:00:00 1970 | 5 | 5 | foo DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING c1, c4;
105 | 5 | 00105 | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5 | 5 | foo c1 | c4
115 | 5 | 00115 | Fri Jan 16 00:00:00 1970 PST | Fri Jan 16 00:00:00 1970 | 5 | 5 | foo ------+------------------------------
125 | 5 | 00125 | Mon Jan 26 00:00:00 1970 PST | Mon Jan 26 00:00:00 1970 | 5 | 5 | foo 5 | Tue Jan 06 00:00:00 1970 PST
135 | 5 | 00135 | Thu Feb 05 00:00:00 1970 PST | Thu Feb 05 00:00:00 1970 | 5 | 5 | foo 15 | Fri Jan 16 00:00:00 1970 PST
145 | 5 | 00145 | Sun Feb 15 00:00:00 1970 PST | Sun Feb 15 00:00:00 1970 | 5 | 5 | foo 25 | Mon Jan 26 00:00:00 1970 PST
155 | 5 | 00155 | Wed Feb 25 00:00:00 1970 PST | Wed Feb 25 00:00:00 1970 | 5 | 5 | foo 35 | Thu Feb 05 00:00:00 1970 PST
165 | 5 | 00165 | Sat Mar 07 00:00:00 1970 PST | Sat Mar 07 00:00:00 1970 | 5 | 5 | foo 45 | Sun Feb 15 00:00:00 1970 PST
175 | 5 | 00175 | Tue Mar 17 00:00:00 1970 PST | Tue Mar 17 00:00:00 1970 | 5 | 5 | foo 55 | Wed Feb 25 00:00:00 1970 PST
185 | 5 | 00185 | Fri Mar 27 00:00:00 1970 PST | Fri Mar 27 00:00:00 1970 | 5 | 5 | foo 65 | Sat Mar 07 00:00:00 1970 PST
195 | 5 | 00195 | Mon Apr 06 00:00:00 1970 PST | Mon Apr 06 00:00:00 1970 | 5 | 5 | foo 75 | Tue Mar 17 00:00:00 1970 PST
205 | 5 | 00205 | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5 | 5 | foo 85 | Fri Mar 27 00:00:00 1970 PST
215 | 5 | 00215 | Fri Jan 16 00:00:00 1970 PST | Fri Jan 16 00:00:00 1970 | 5 | 5 | foo 95 | Mon Apr 06 00:00:00 1970 PST
225 | 5 | 00225 | Mon Jan 26 00:00:00 1970 PST | Mon Jan 26 00:00:00 1970 | 5 | 5 | foo 105 | Tue Jan 06 00:00:00 1970 PST
235 | 5 | 00235 | Thu Feb 05 00:00:00 1970 PST | Thu Feb 05 00:00:00 1970 | 5 | 5 | foo 115 | Fri Jan 16 00:00:00 1970 PST
245 | 5 | 00245 | Sun Feb 15 00:00:00 1970 PST | Sun Feb 15 00:00:00 1970 | 5 | 5 | foo 125 | Mon Jan 26 00:00:00 1970 PST
255 | 5 | 00255 | Wed Feb 25 00:00:00 1970 PST | Wed Feb 25 00:00:00 1970 | 5 | 5 | foo 135 | Thu Feb 05 00:00:00 1970 PST
265 | 5 | 00265 | Sat Mar 07 00:00:00 1970 PST | Sat Mar 07 00:00:00 1970 | 5 | 5 | foo 145 | Sun Feb 15 00:00:00 1970 PST
275 | 5 | 00275 | Tue Mar 17 00:00:00 1970 PST | Tue Mar 17 00:00:00 1970 | 5 | 5 | foo 155 | Wed Feb 25 00:00:00 1970 PST
285 | 5 | 00285 | Fri Mar 27 00:00:00 1970 PST | Fri Mar 27 00:00:00 1970 | 5 | 5 | foo 165 | Sat Mar 07 00:00:00 1970 PST
295 | 5 | 00295 | Mon Apr 06 00:00:00 1970 PST | Mon Apr 06 00:00:00 1970 | 5 | 5 | foo 175 | Tue Mar 17 00:00:00 1970 PST
305 | 5 | 00305 | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5 | 5 | foo 185 | Fri Mar 27 00:00:00 1970 PST
315 | 5 | 00315 | Fri Jan 16 00:00:00 1970 PST | Fri Jan 16 00:00:00 1970 | 5 | 5 | foo 195 | Mon Apr 06 00:00:00 1970 PST
325 | 5 | 00325 | Mon Jan 26 00:00:00 1970 PST | Mon Jan 26 00:00:00 1970 | 5 | 5 | foo 205 | Tue Jan 06 00:00:00 1970 PST
335 | 5 | 00335 | Thu Feb 05 00:00:00 1970 PST | Thu Feb 05 00:00:00 1970 | 5 | 5 | foo 215 | Fri Jan 16 00:00:00 1970 PST
345 | 5 | 00345 | Sun Feb 15 00:00:00 1970 PST | Sun Feb 15 00:00:00 1970 | 5 | 5 | foo 225 | Mon Jan 26 00:00:00 1970 PST
355 | 5 | 00355 | Wed Feb 25 00:00:00 1970 PST | Wed Feb 25 00:00:00 1970 | 5 | 5 | foo 235 | Thu Feb 05 00:00:00 1970 PST
365 | 5 | 00365 | Sat Mar 07 00:00:00 1970 PST | Sat Mar 07 00:00:00 1970 | 5 | 5 | foo 245 | Sun Feb 15 00:00:00 1970 PST
375 | 5 | 00375 | Tue Mar 17 00:00:00 1970 PST | Tue Mar 17 00:00:00 1970 | 5 | 5 | foo 255 | Wed Feb 25 00:00:00 1970 PST
385 | 5 | 00385 | Fri Mar 27 00:00:00 1970 PST | Fri Mar 27 00:00:00 1970 | 5 | 5 | foo 265 | Sat Mar 07 00:00:00 1970 PST
395 | 5 | 00395 | Mon Apr 06 00:00:00 1970 PST | Mon Apr 06 00:00:00 1970 | 5 | 5 | foo 275 | Tue Mar 17 00:00:00 1970 PST
405 | 5 | 00405 | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5 | 5 | foo 285 | Fri Mar 27 00:00:00 1970 PST
415 | 5 | 00415 | Fri Jan 16 00:00:00 1970 PST | Fri Jan 16 00:00:00 1970 | 5 | 5 | foo 295 | Mon Apr 06 00:00:00 1970 PST
425 | 5 | 00425 | Mon Jan 26 00:00:00 1970 PST | Mon Jan 26 00:00:00 1970 | 5 | 5 | foo 305 | Tue Jan 06 00:00:00 1970 PST
435 | 5 | 00435 | Thu Feb 05 00:00:00 1970 PST | Thu Feb 05 00:00:00 1970 | 5 | 5 | foo 315 | Fri Jan 16 00:00:00 1970 PST
445 | 5 | 00445 | Sun Feb 15 00:00:00 1970 PST | Sun Feb 15 00:00:00 1970 | 5 | 5 | foo 325 | Mon Jan 26 00:00:00 1970 PST
455 | 5 | 00455 | Wed Feb 25 00:00:00 1970 PST | Wed Feb 25 00:00:00 1970 | 5 | 5 | foo 335 | Thu Feb 05 00:00:00 1970 PST
465 | 5 | 00465 | Sat Mar 07 00:00:00 1970 PST | Sat Mar 07 00:00:00 1970 | 5 | 5 | foo 345 | Sun Feb 15 00:00:00 1970 PST
475 | 5 | 00475 | Tue Mar 17 00:00:00 1970 PST | Tue Mar 17 00:00:00 1970 | 5 | 5 | foo 355 | Wed Feb 25 00:00:00 1970 PST
485 | 5 | 00485 | Fri Mar 27 00:00:00 1970 PST | Fri Mar 27 00:00:00 1970 | 5 | 5 | foo 365 | Sat Mar 07 00:00:00 1970 PST
495 | 5 | 00495 | Mon Apr 06 00:00:00 1970 PST | Mon Apr 06 00:00:00 1970 | 5 | 5 | foo 375 | Tue Mar 17 00:00:00 1970 PST
505 | 5 | 00505 | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5 | 5 | foo 385 | Fri Mar 27 00:00:00 1970 PST
515 | 5 | 00515 | Fri Jan 16 00:00:00 1970 PST | Fri Jan 16 00:00:00 1970 | 5 | 5 | foo 395 | Mon Apr 06 00:00:00 1970 PST
525 | 5 | 00525 | Mon Jan 26 00:00:00 1970 PST | Mon Jan 26 00:00:00 1970 | 5 | 5 | foo 405 | Tue Jan 06 00:00:00 1970 PST
535 | 5 | 00535 | Thu Feb 05 00:00:00 1970 PST | Thu Feb 05 00:00:00 1970 | 5 | 5 | foo 415 | Fri Jan 16 00:00:00 1970 PST
545 | 5 | 00545 | Sun Feb 15 00:00:00 1970 PST | Sun Feb 15 00:00:00 1970 | 5 | 5 | foo 425 | Mon Jan 26 00:00:00 1970 PST
555 | 5 | 00555 | Wed Feb 25 00:00:00 1970 PST | Wed Feb 25 00:00:00 1970 | 5 | 5 | foo 435 | Thu Feb 05 00:00:00 1970 PST
565 | 5 | 00565 | Sat Mar 07 00:00:00 1970 PST | Sat Mar 07 00:00:00 1970 | 5 | 5 | foo 445 | Sun Feb 15 00:00:00 1970 PST
575 | 5 | 00575 | Tue Mar 17 00:00:00 1970 PST | Tue Mar 17 00:00:00 1970 | 5 | 5 | foo 455 | Wed Feb 25 00:00:00 1970 PST
585 | 5 | 00585 | Fri Mar 27 00:00:00 1970 PST | Fri Mar 27 00:00:00 1970 | 5 | 5 | foo 465 | Sat Mar 07 00:00:00 1970 PST
595 | 5 | 00595 | Mon Apr 06 00:00:00 1970 PST | Mon Apr 06 00:00:00 1970 | 5 | 5 | foo 475 | Tue Mar 17 00:00:00 1970 PST
605 | 5 | 00605 | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5 | 5 | foo 485 | Fri Mar 27 00:00:00 1970 PST
615 | 5 | 00615 | Fri Jan 16 00:00:00 1970 PST | Fri Jan 16 00:00:00 1970 | 5 | 5 | foo 495 | Mon Apr 06 00:00:00 1970 PST
625 | 5 | 00625 | Mon Jan 26 00:00:00 1970 PST | Mon Jan 26 00:00:00 1970 | 5 | 5 | foo 505 | Tue Jan 06 00:00:00 1970 PST
635 | 5 | 00635 | Thu Feb 05 00:00:00 1970 PST | Thu Feb 05 00:00:00 1970 | 5 | 5 | foo 515 | Fri Jan 16 00:00:00 1970 PST
645 | 5 | 00645 | Sun Feb 15 00:00:00 1970 PST | Sun Feb 15 00:00:00 1970 | 5 | 5 | foo 525 | Mon Jan 26 00:00:00 1970 PST
655 | 5 | 00655 | Wed Feb 25 00:00:00 1970 PST | Wed Feb 25 00:00:00 1970 | 5 | 5 | foo 535 | Thu Feb 05 00:00:00 1970 PST
665 | 5 | 00665 | Sat Mar 07 00:00:00 1970 PST | Sat Mar 07 00:00:00 1970 | 5 | 5 | foo 545 | Sun Feb 15 00:00:00 1970 PST
675 | 5 | 00675 | Tue Mar 17 00:00:00 1970 PST | Tue Mar 17 00:00:00 1970 | 5 | 5 | foo 555 | Wed Feb 25 00:00:00 1970 PST
685 | 5 | 00685 | Fri Mar 27 00:00:00 1970 PST | Fri Mar 27 00:00:00 1970 | 5 | 5 | foo 565 | Sat Mar 07 00:00:00 1970 PST
695 | 5 | 00695 | Mon Apr 06 00:00:00 1970 PST | Mon Apr 06 00:00:00 1970 | 5 | 5 | foo 575 | Tue Mar 17 00:00:00 1970 PST
705 | 5 | 00705 | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5 | 5 | foo 585 | Fri Mar 27 00:00:00 1970 PST
715 | 5 | 00715 | Fri Jan 16 00:00:00 1970 PST | Fri Jan 16 00:00:00 1970 | 5 | 5 | foo 595 | Mon Apr 06 00:00:00 1970 PST
725 | 5 | 00725 | Mon Jan 26 00:00:00 1970 PST | Mon Jan 26 00:00:00 1970 | 5 | 5 | foo 605 | Tue Jan 06 00:00:00 1970 PST
735 | 5 | 00735 | Thu Feb 05 00:00:00 1970 PST | Thu Feb 05 00:00:00 1970 | 5 | 5 | foo 615 | Fri Jan 16 00:00:00 1970 PST
745 | 5 | 00745 | Sun Feb 15 00:00:00 1970 PST | Sun Feb 15 00:00:00 1970 | 5 | 5 | foo 625 | Mon Jan 26 00:00:00 1970 PST
755 | 5 | 00755 | Wed Feb 25 00:00:00 1970 PST | Wed Feb 25 00:00:00 1970 | 5 | 5 | foo 635 | Thu Feb 05 00:00:00 1970 PST
765 | 5 | 00765 | Sat Mar 07 00:00:00 1970 PST | Sat Mar 07 00:00:00 1970 | 5 | 5 | foo 645 | Sun Feb 15 00:00:00 1970 PST
775 | 5 | 00775 | Tue Mar 17 00:00:00 1970 PST | Tue Mar 17 00:00:00 1970 | 5 | 5 | foo 655 | Wed Feb 25 00:00:00 1970 PST
785 | 5 | 00785 | Fri Mar 27 00:00:00 1970 PST | Fri Mar 27 00:00:00 1970 | 5 | 5 | foo 665 | Sat Mar 07 00:00:00 1970 PST
795 | 5 | 00795 | Mon Apr 06 00:00:00 1970 PST | Mon Apr 06 00:00:00 1970 | 5 | 5 | foo 675 | Tue Mar 17 00:00:00 1970 PST
805 | 5 | 00805 | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5 | 5 | foo 685 | Fri Mar 27 00:00:00 1970 PST
815 | 5 | 00815 | Fri Jan 16 00:00:00 1970 PST | Fri Jan 16 00:00:00 1970 | 5 | 5 | foo 695 | Mon Apr 06 00:00:00 1970 PST
825 | 5 | 00825 | Mon Jan 26 00:00:00 1970 PST | Mon Jan 26 00:00:00 1970 | 5 | 5 | foo 705 | Tue Jan 06 00:00:00 1970 PST
835 | 5 | 00835 | Thu Feb 05 00:00:00 1970 PST | Thu Feb 05 00:00:00 1970 | 5 | 5 | foo 715 | Fri Jan 16 00:00:00 1970 PST
845 | 5 | 00845 | Sun Feb 15 00:00:00 1970 PST | Sun Feb 15 00:00:00 1970 | 5 | 5 | foo 725 | Mon Jan 26 00:00:00 1970 PST
855 | 5 | 00855 | Wed Feb 25 00:00:00 1970 PST | Wed Feb 25 00:00:00 1970 | 5 | 5 | foo 735 | Thu Feb 05 00:00:00 1970 PST
865 | 5 | 00865 | Sat Mar 07 00:00:00 1970 PST | Sat Mar 07 00:00:00 1970 | 5 | 5 | foo 745 | Sun Feb 15 00:00:00 1970 PST
875 | 5 | 00875 | Tue Mar 17 00:00:00 1970 PST | Tue Mar 17 00:00:00 1970 | 5 | 5 | foo 755 | Wed Feb 25 00:00:00 1970 PST
885 | 5 | 00885 | Fri Mar 27 00:00:00 1970 PST | Fri Mar 27 00:00:00 1970 | 5 | 5 | foo 765 | Sat Mar 07 00:00:00 1970 PST
895 | 5 | 00895 | Mon Apr 06 00:00:00 1970 PST | Mon Apr 06 00:00:00 1970 | 5 | 5 | foo 775 | Tue Mar 17 00:00:00 1970 PST
905 | 5 | 00905 | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5 | 5 | foo 785 | Fri Mar 27 00:00:00 1970 PST
915 | 5 | 00915 | Fri Jan 16 00:00:00 1970 PST | Fri Jan 16 00:00:00 1970 | 5 | 5 | foo 795 | Mon Apr 06 00:00:00 1970 PST
925 | 5 | 00925 | Mon Jan 26 00:00:00 1970 PST | Mon Jan 26 00:00:00 1970 | 5 | 5 | foo 805 | Tue Jan 06 00:00:00 1970 PST
935 | 5 | 00935 | Thu Feb 05 00:00:00 1970 PST | Thu Feb 05 00:00:00 1970 | 5 | 5 | foo 815 | Fri Jan 16 00:00:00 1970 PST
945 | 5 | 00945 | Sun Feb 15 00:00:00 1970 PST | Sun Feb 15 00:00:00 1970 | 5 | 5 | foo 825 | Mon Jan 26 00:00:00 1970 PST
955 | 5 | 00955 | Wed Feb 25 00:00:00 1970 PST | Wed Feb 25 00:00:00 1970 | 5 | 5 | foo 835 | Thu Feb 05 00:00:00 1970 PST
965 | 5 | 00965 | Sat Mar 07 00:00:00 1970 PST | Sat Mar 07 00:00:00 1970 | 5 | 5 | foo 845 | Sun Feb 15 00:00:00 1970 PST
975 | 5 | 00975 | Tue Mar 17 00:00:00 1970 PST | Tue Mar 17 00:00:00 1970 | 5 | 5 | foo 855 | Wed Feb 25 00:00:00 1970 PST
985 | 5 | 00985 | Fri Mar 27 00:00:00 1970 PST | Fri Mar 27 00:00:00 1970 | 5 | 5 | foo 865 | Sat Mar 07 00:00:00 1970 PST
995 | 5 | 00995 | Mon Apr 06 00:00:00 1970 PST | Mon Apr 06 00:00:00 1970 | 5 | 5 | foo 875 | Tue Mar 17 00:00:00 1970 PST
1005 | 105 | 0000500005 | | | | ft2 | 885 | Fri Mar 27 00:00:00 1970 PST
1015 | 105 | 0001500015 | | | | ft2 | 895 | Mon Apr 06 00:00:00 1970 PST
1105 | 205 | eee | | | | ft2 | 905 | Tue Jan 06 00:00:00 1970 PST
915 | Fri Jan 16 00:00:00 1970 PST
925 | Mon Jan 26 00:00:00 1970 PST
935 | Thu Feb 05 00:00:00 1970 PST
945 | Sun Feb 15 00:00:00 1970 PST
955 | Wed Feb 25 00:00:00 1970 PST
965 | Sat Mar 07 00:00:00 1970 PST
975 | Tue Mar 17 00:00:00 1970 PST
985 | Fri Mar 27 00:00:00 1970 PST
995 | Mon Apr 06 00:00:00 1970 PST
1005 |
1015 |
1105 |
(103 rows) (103 rows)
EXPLAIN (verbose, costs off) EXPLAIN (verbose, costs off)
...@@ -1097,7 +1109,7 @@ DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2; ...@@ -1097,7 +1109,7 @@ DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2;
Hash Cond: (ft2.c2 = ft1.c1) Hash Cond: (ft2.c2 = ft1.c1)
-> Foreign Scan on public.ft2 -> Foreign Scan on public.ft2
Output: ft2.ctid, ft2.c2 Output: ft2.ctid, ft2.c2
Remote SQL: SELECT NULL, c2, NULL, NULL, NULL, NULL, NULL, NULL, ctid FROM "S 1"."T 1" FOR UPDATE Remote SQL: SELECT c2, ctid FROM "S 1"."T 1" FOR UPDATE
-> Hash -> Hash
Output: ft1.*, ft1.c1 Output: ft1.*, ft1.c1
-> Foreign Scan on public.ft1 -> Foreign Scan on public.ft1
......
...@@ -84,9 +84,10 @@ typedef struct PgFdwRelationInfo ...@@ -84,9 +84,10 @@ typedef struct PgFdwRelationInfo
* Indexes of FDW-private information stored in fdw_private lists. * Indexes of FDW-private information stored in fdw_private lists.
* *
* We store various information in ForeignScan.fdw_private to pass it from * We store various information in ForeignScan.fdw_private to pass it from
* planner to executor. Currently there is just: * planner to executor. Currently we store:
* *
* 1) SELECT statement text to be sent to the remote server * 1) SELECT statement text to be sent to the remote server
* 2) Integer list of attribute numbers retrieved by the SELECT
* *
* These items are indexed with the enum FdwScanPrivateIndex, so an item * These items are indexed with the enum FdwScanPrivateIndex, so an item
* can be fetched with list_nth(). For example, to get the SELECT statement: * can be fetched with list_nth(). For example, to get the SELECT statement:
...@@ -95,7 +96,9 @@ typedef struct PgFdwRelationInfo ...@@ -95,7 +96,9 @@ typedef struct PgFdwRelationInfo
enum FdwScanPrivateIndex enum FdwScanPrivateIndex
{ {
/* SQL statement to execute remotely (as a String node) */ /* SQL statement to execute remotely (as a String node) */
FdwScanPrivateSelectSql FdwScanPrivateSelectSql,
/* Integer list of attribute numbers retrieved by the SELECT */
FdwScanPrivateRetrievedAttrs
}; };
/* /*
...@@ -106,6 +109,7 @@ enum FdwScanPrivateIndex ...@@ -106,6 +109,7 @@ enum FdwScanPrivateIndex
* 2) Integer list of target attribute numbers for INSERT/UPDATE * 2) Integer list of target attribute numbers for INSERT/UPDATE
* (NIL for a DELETE) * (NIL for a DELETE)
* 3) Boolean flag showing if there's a RETURNING clause * 3) Boolean flag showing if there's a RETURNING clause
* 4) Integer list of attribute numbers retrieved by RETURNING, if any
*/ */
enum FdwModifyPrivateIndex enum FdwModifyPrivateIndex
{ {
...@@ -114,7 +118,9 @@ enum FdwModifyPrivateIndex ...@@ -114,7 +118,9 @@ enum FdwModifyPrivateIndex
/* Integer list of target attribute numbers for INSERT/UPDATE */ /* Integer list of target attribute numbers for INSERT/UPDATE */
FdwModifyPrivateTargetAttnums, FdwModifyPrivateTargetAttnums,
/* has-returning flag (as an integer Value node) */ /* has-returning flag (as an integer Value node) */
FdwModifyPrivateHasReturning FdwModifyPrivateHasReturning,
/* Integer list of attribute numbers retrieved by RETURNING */
FdwModifyPrivateRetrievedAttrs
}; };
/* /*
...@@ -125,7 +131,9 @@ typedef struct PgFdwScanState ...@@ -125,7 +131,9 @@ typedef struct PgFdwScanState
Relation rel; /* relcache entry for the foreign table */ Relation rel; /* relcache entry for the foreign table */
AttInMetadata *attinmeta; /* attribute datatype conversion metadata */ AttInMetadata *attinmeta; /* attribute datatype conversion metadata */
List *fdw_private; /* FDW-private information from planner */ /* extracted fdw_private data */
char *query; /* text of SELECT command */
List *retrieved_attrs; /* list of retrieved attribute numbers */
/* for remote query execution */ /* for remote query execution */
PGconn *conn; /* connection for the scan */ PGconn *conn; /* connection for the scan */
...@@ -166,6 +174,7 @@ typedef struct PgFdwModifyState ...@@ -166,6 +174,7 @@ typedef struct PgFdwModifyState
char *query; /* text of INSERT/UPDATE/DELETE command */ char *query; /* text of INSERT/UPDATE/DELETE command */
List *target_attrs; /* list of target attribute numbers */ List *target_attrs; /* list of target attribute numbers */
bool has_returning; /* is there a RETURNING clause? */ bool has_returning; /* is there a RETURNING clause? */
List *retrieved_attrs; /* attr numbers retrieved by RETURNING */
/* info about parameters for prepared statement */ /* info about parameters for prepared statement */
AttrNumber ctidAttno; /* attnum of input resjunk ctid column */ AttrNumber ctidAttno; /* attnum of input resjunk ctid column */
...@@ -183,6 +192,7 @@ typedef struct PgFdwAnalyzeState ...@@ -183,6 +192,7 @@ typedef struct PgFdwAnalyzeState
{ {
Relation rel; /* relcache entry for the foreign table */ Relation rel; /* relcache entry for the foreign table */
AttInMetadata *attinmeta; /* attribute datatype conversion metadata */ AttInMetadata *attinmeta; /* attribute datatype conversion metadata */
List *retrieved_attrs; /* attr numbers retrieved by query */
/* collected sample rows */ /* collected sample rows */
HeapTuple *rows; /* array of size targrows */ HeapTuple *rows; /* array of size targrows */
...@@ -314,6 +324,7 @@ static HeapTuple make_tuple_from_result_row(PGresult *res, ...@@ -314,6 +324,7 @@ static HeapTuple make_tuple_from_result_row(PGresult *res,
int row, int row,
Relation rel, Relation rel,
AttInMetadata *attinmeta, AttInMetadata *attinmeta,
List *retrieved_attrs,
MemoryContext temp_context); MemoryContext temp_context);
static void conversion_error_callback(void *arg); static void conversion_error_callback(void *arg);
...@@ -728,6 +739,7 @@ postgresGetForeignPlan(PlannerInfo *root, ...@@ -728,6 +739,7 @@ postgresGetForeignPlan(PlannerInfo *root,
List *remote_conds = NIL; List *remote_conds = NIL;
List *local_exprs = NIL; List *local_exprs = NIL;
List *params_list = NIL; List *params_list = NIL;
List *retrieved_attrs;
StringInfoData sql; StringInfoData sql;
ListCell *lc; ListCell *lc;
...@@ -777,7 +789,8 @@ postgresGetForeignPlan(PlannerInfo *root, ...@@ -777,7 +789,8 @@ postgresGetForeignPlan(PlannerInfo *root,
* expressions to be sent as parameters. * expressions to be sent as parameters.
*/ */
initStringInfo(&sql); initStringInfo(&sql);
deparseSelectSql(&sql, root, baserel, fpinfo->attrs_used); deparseSelectSql(&sql, root, baserel, fpinfo->attrs_used,
&retrieved_attrs);
if (remote_conds) if (remote_conds)
appendWhereClause(&sql, root, baserel, remote_conds, appendWhereClause(&sql, root, baserel, remote_conds,
true, &params_list); true, &params_list);
...@@ -829,7 +842,8 @@ postgresGetForeignPlan(PlannerInfo *root, ...@@ -829,7 +842,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 enum FdwScanPrivateIndex, above. * Items in the list must match enum FdwScanPrivateIndex, above.
*/ */
fdw_private = list_make1(makeString(sql.data)); fdw_private = list_make2(makeString(sql.data),
retrieved_attrs);
/* /*
* Create the ForeignScan node from target list, local filtering * Create the ForeignScan node from target list, local filtering
...@@ -901,7 +915,10 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags) ...@@ -901,7 +915,10 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
fsstate->cursor_exists = false; fsstate->cursor_exists = false;
/* Get private info created by planner functions. */ /* Get private info created by planner functions. */
fsstate->fdw_private = fsplan->fdw_private; fsstate->query = strVal(list_nth(fsplan->fdw_private,
FdwScanPrivateSelectSql));
fsstate->retrieved_attrs = (List *) list_nth(fsplan->fdw_private,
FdwScanPrivateRetrievedAttrs);
/* Create contexts for batches of tuples and per-tuple temp workspace. */ /* Create contexts for batches of tuples and per-tuple temp workspace. */
fsstate->batch_cxt = AllocSetContextCreate(estate->es_query_cxt, fsstate->batch_cxt = AllocSetContextCreate(estate->es_query_cxt,
...@@ -915,7 +932,7 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags) ...@@ -915,7 +932,7 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
ALLOCSET_SMALL_INITSIZE, ALLOCSET_SMALL_INITSIZE,
ALLOCSET_SMALL_MAXSIZE); ALLOCSET_SMALL_MAXSIZE);
/* Get info we'll need for data conversion. */ /* Get info we'll need for input data conversion. */
fsstate->attinmeta = TupleDescGetAttInMetadata(RelationGetDescr(fsstate->rel)); fsstate->attinmeta = TupleDescGetAttInMetadata(RelationGetDescr(fsstate->rel));
/* Prepare for output conversion of parameters used in remote query. */ /* Prepare for output conversion of parameters used in remote query. */
...@@ -1138,6 +1155,7 @@ postgresPlanForeignModify(PlannerInfo *root, ...@@ -1138,6 +1155,7 @@ postgresPlanForeignModify(PlannerInfo *root,
StringInfoData sql; StringInfoData sql;
List *targetAttrs = NIL; List *targetAttrs = NIL;
List *returningList = NIL; List *returningList = NIL;
List *retrieved_attrs = NIL;
initStringInfo(&sql); initStringInfo(&sql);
...@@ -1194,15 +1212,18 @@ postgresPlanForeignModify(PlannerInfo *root, ...@@ -1194,15 +1212,18 @@ postgresPlanForeignModify(PlannerInfo *root,
{ {
case CMD_INSERT: case CMD_INSERT:
deparseInsertSql(&sql, root, resultRelation, rel, deparseInsertSql(&sql, root, resultRelation, rel,
targetAttrs, returningList); targetAttrs, returningList,
&retrieved_attrs);
break; break;
case CMD_UPDATE: case CMD_UPDATE:
deparseUpdateSql(&sql, root, resultRelation, rel, deparseUpdateSql(&sql, root, resultRelation, rel,
targetAttrs, returningList); targetAttrs, returningList,
&retrieved_attrs);
break; break;
case CMD_DELETE: case CMD_DELETE:
deparseDeleteSql(&sql, root, resultRelation, rel, deparseDeleteSql(&sql, root, resultRelation, rel,
returningList); returningList,
&retrieved_attrs);
break; break;
default: default:
elog(ERROR, "unexpected operation: %d", (int) operation); elog(ERROR, "unexpected operation: %d", (int) operation);
...@@ -1215,9 +1236,10 @@ postgresPlanForeignModify(PlannerInfo *root, ...@@ -1215,9 +1236,10 @@ postgresPlanForeignModify(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 enum FdwModifyPrivateIndex, above. * Items in the list must match enum FdwModifyPrivateIndex, above.
*/ */
return list_make3(makeString(sql.data), return list_make4(makeString(sql.data),
targetAttrs, targetAttrs,
makeInteger((returningList != NIL))); makeInteger((returningList != NIL)),
retrieved_attrs);
} }
/* /*
...@@ -1279,6 +1301,8 @@ postgresBeginForeignModify(ModifyTableState *mtstate, ...@@ -1279,6 +1301,8 @@ postgresBeginForeignModify(ModifyTableState *mtstate,
FdwModifyPrivateTargetAttnums); FdwModifyPrivateTargetAttnums);
fmstate->has_returning = intVal(list_nth(fdw_private, fmstate->has_returning = intVal(list_nth(fdw_private,
FdwModifyPrivateHasReturning)); FdwModifyPrivateHasReturning));
fmstate->retrieved_attrs = (List *) list_nth(fdw_private,
FdwModifyPrivateRetrievedAttrs);
/* Create context for per-tuple temp workspace. */ /* Create context for per-tuple temp workspace. */
fmstate->temp_cxt = AllocSetContextCreate(estate->es_query_cxt, fmstate->temp_cxt = AllocSetContextCreate(estate->es_query_cxt,
...@@ -1641,6 +1665,7 @@ estimate_path_cost_size(PlannerInfo *root, ...@@ -1641,6 +1665,7 @@ estimate_path_cost_size(PlannerInfo *root,
if (fpinfo->use_remote_estimate) if (fpinfo->use_remote_estimate)
{ {
StringInfoData sql; StringInfoData sql;
List *retrieved_attrs;
PGconn *conn; PGconn *conn;
/* /*
...@@ -1650,7 +1675,8 @@ estimate_path_cost_size(PlannerInfo *root, ...@@ -1650,7 +1675,8 @@ estimate_path_cost_size(PlannerInfo *root,
*/ */
initStringInfo(&sql); initStringInfo(&sql);
appendStringInfoString(&sql, "EXPLAIN "); appendStringInfoString(&sql, "EXPLAIN ");
deparseSelectSql(&sql, root, baserel, fpinfo->attrs_used); deparseSelectSql(&sql, root, baserel, fpinfo->attrs_used,
&retrieved_attrs);
if (fpinfo->remote_conds) if (fpinfo->remote_conds)
appendWhereClause(&sql, root, baserel, fpinfo->remote_conds, appendWhereClause(&sql, root, baserel, fpinfo->remote_conds,
true, NULL); true, NULL);
...@@ -1819,7 +1845,6 @@ create_cursor(ForeignScanState *node) ...@@ -1819,7 +1845,6 @@ create_cursor(ForeignScanState *node)
int numParams = fsstate->numParams; int numParams = fsstate->numParams;
const char **values = fsstate->param_values; const char **values = fsstate->param_values;
PGconn *conn = fsstate->conn; PGconn *conn = fsstate->conn;
char *sql;
StringInfoData buf; StringInfoData buf;
PGresult *res; PGresult *res;
...@@ -1867,10 +1892,9 @@ create_cursor(ForeignScanState *node) ...@@ -1867,10 +1892,9 @@ create_cursor(ForeignScanState *node)
} }
/* Construct the DECLARE CURSOR command */ /* Construct the DECLARE CURSOR command */
sql = strVal(list_nth(fsstate->fdw_private, FdwScanPrivateSelectSql));
initStringInfo(&buf); initStringInfo(&buf);
appendStringInfo(&buf, "DECLARE c%u CURSOR FOR\n%s", appendStringInfo(&buf, "DECLARE c%u CURSOR FOR\n%s",
fsstate->cursor_number, sql); fsstate->cursor_number, fsstate->query);
/* /*
* Notice that we pass NULL for paramTypes, thus forcing the remote server * Notice that we pass NULL for paramTypes, thus forcing the remote server
...@@ -1885,7 +1909,7 @@ create_cursor(ForeignScanState *node) ...@@ -1885,7 +1909,7 @@ create_cursor(ForeignScanState *node)
res = PQexecParams(conn, buf.data, numParams, NULL, values, res = PQexecParams(conn, buf.data, numParams, NULL, values,
NULL, NULL, 0); NULL, NULL, 0);
if (PQresultStatus(res) != PGRES_COMMAND_OK) if (PQresultStatus(res) != PGRES_COMMAND_OK)
pgfdw_report_error(ERROR, res, true, sql); pgfdw_report_error(ERROR, res, true, fsstate->query);
PQclear(res); PQclear(res);
/* Mark the cursor as created, and show no tuples have been retrieved */ /* Mark the cursor as created, and show no tuples have been retrieved */
...@@ -1936,9 +1960,7 @@ fetch_more_data(ForeignScanState *node) ...@@ -1936,9 +1960,7 @@ fetch_more_data(ForeignScanState *node)
res = PQexec(conn, sql); res = PQexec(conn, sql);
/* On error, report the original query, not the FETCH. */ /* On error, report the original query, not the FETCH. */
if (PQresultStatus(res) != PGRES_TUPLES_OK) if (PQresultStatus(res) != PGRES_TUPLES_OK)
pgfdw_report_error(ERROR, res, false, pgfdw_report_error(ERROR, res, false, fsstate->query);
strVal(list_nth(fsstate->fdw_private,
FdwScanPrivateSelectSql)));
/* Convert the data into HeapTuples */ /* Convert the data into HeapTuples */
numrows = PQntuples(res); numrows = PQntuples(res);
...@@ -1952,6 +1974,7 @@ fetch_more_data(ForeignScanState *node) ...@@ -1952,6 +1974,7 @@ fetch_more_data(ForeignScanState *node)
make_tuple_from_result_row(res, i, make_tuple_from_result_row(res, i,
fsstate->rel, fsstate->rel,
fsstate->attinmeta, fsstate->attinmeta,
fsstate->retrieved_attrs,
fsstate->temp_cxt); fsstate->temp_cxt);
} }
...@@ -2170,6 +2193,7 @@ store_returning_result(PgFdwModifyState *fmstate, ...@@ -2170,6 +2193,7 @@ store_returning_result(PgFdwModifyState *fmstate,
newtup = make_tuple_from_result_row(res, 0, newtup = make_tuple_from_result_row(res, 0,
fmstate->rel, fmstate->rel,
fmstate->attinmeta, fmstate->attinmeta,
fmstate->retrieved_attrs,
fmstate->temp_cxt); fmstate->temp_cxt);
/* tuple will be deleted when it is cleared from the slot */ /* tuple will be deleted when it is cleared from the slot */
ExecStoreTuple(newtup, slot, InvalidBuffer, true); ExecStoreTuple(newtup, slot, InvalidBuffer, true);
...@@ -2316,7 +2340,7 @@ postgresAcquireSampleRowsFunc(Relation relation, int elevel, ...@@ -2316,7 +2340,7 @@ postgresAcquireSampleRowsFunc(Relation relation, int elevel,
cursor_number = GetCursorNumber(conn); cursor_number = GetCursorNumber(conn);
initStringInfo(&sql); initStringInfo(&sql);
appendStringInfo(&sql, "DECLARE c%u CURSOR FOR ", cursor_number); appendStringInfo(&sql, "DECLARE c%u CURSOR FOR ", cursor_number);
deparseAnalyzeSql(&sql, relation); deparseAnalyzeSql(&sql, relation, &astate.retrieved_attrs);
/* In what follows, do not risk leaking any PGresults. */ /* In what follows, do not risk leaking any PGresults. */
PG_TRY(); PG_TRY();
...@@ -2461,6 +2485,7 @@ analyze_row_processor(PGresult *res, int row, PgFdwAnalyzeState *astate) ...@@ -2461,6 +2485,7 @@ analyze_row_processor(PGresult *res, int row, PgFdwAnalyzeState *astate)
astate->rows[pos] = make_tuple_from_result_row(res, row, astate->rows[pos] = make_tuple_from_result_row(res, row,
astate->rel, astate->rel,
astate->attinmeta, astate->attinmeta,
astate->retrieved_attrs,
astate->temp_cxt); astate->temp_cxt);
MemoryContextSwitchTo(oldcontext); MemoryContextSwitchTo(oldcontext);
...@@ -2471,26 +2496,27 @@ analyze_row_processor(PGresult *res, int row, PgFdwAnalyzeState *astate) ...@@ -2471,26 +2496,27 @@ analyze_row_processor(PGresult *res, int row, PgFdwAnalyzeState *astate)
* Create a tuple from the specified row of the PGresult. * Create a tuple from the specified row of the PGresult.
* *
* rel is the local representation of the foreign table, attinmeta is * rel is the local representation of the foreign table, attinmeta is
* conversion data for the rel's tupdesc, and temp_context is a working * conversion data for the rel's tupdesc, and retrieved_attrs is an
* context that can be reset after each tuple. * integer list of the table column numbers present in the PGresult.
* temp_context is a working context that can be reset after each tuple.
*/ */
static HeapTuple static HeapTuple
make_tuple_from_result_row(PGresult *res, make_tuple_from_result_row(PGresult *res,
int row, int row,
Relation rel, Relation rel,
AttInMetadata *attinmeta, AttInMetadata *attinmeta,
List *retrieved_attrs,
MemoryContext temp_context) MemoryContext temp_context)
{ {
HeapTuple tuple; HeapTuple tuple;
TupleDesc tupdesc = RelationGetDescr(rel); TupleDesc tupdesc = RelationGetDescr(rel);
Form_pg_attribute *attrs = tupdesc->attrs;
Datum *values; Datum *values;
bool *nulls; bool *nulls;
ItemPointer ctid = NULL; ItemPointer ctid = NULL;
ConversionLocation errpos; ConversionLocation errpos;
ErrorContextCallback errcallback; ErrorContextCallback errcallback;
MemoryContext oldcontext; MemoryContext oldcontext;
int i; ListCell *lc;
int j; int j;
Assert(row < PQntuples(res)); Assert(row < PQntuples(res));
...@@ -2502,8 +2528,10 @@ make_tuple_from_result_row(PGresult *res, ...@@ -2502,8 +2528,10 @@ make_tuple_from_result_row(PGresult *res,
*/ */
oldcontext = MemoryContextSwitchTo(temp_context); oldcontext = MemoryContextSwitchTo(temp_context);
values = (Datum *) palloc(tupdesc->natts * sizeof(Datum)); values = (Datum *) palloc0(tupdesc->natts * sizeof(Datum));
nulls = (bool *) palloc(tupdesc->natts * sizeof(bool)); nulls = (bool *) palloc(tupdesc->natts * sizeof(bool));
/* Initialize to nulls for any columns not present in result */
memset(nulls, true, tupdesc->natts * sizeof(bool));
/* /*
* Set up and install callback to report where conversion error occurs. * Set up and install callback to report where conversion error occurs.
...@@ -2517,63 +2545,56 @@ make_tuple_from_result_row(PGresult *res, ...@@ -2517,63 +2545,56 @@ make_tuple_from_result_row(PGresult *res,
/* /*
* i indexes columns in the relation, j indexes columns in the PGresult. * i indexes columns in the relation, j indexes columns in the PGresult.
* We assume dropped columns are not represented in the PGresult.
*/ */
for (i = 0, j = 0; i < tupdesc->natts; i++) j = 0;
foreach(lc, retrieved_attrs)
{ {
int i = lfirst_int(lc);
char *valstr; char *valstr;
/* skip dropped columns. */ /* fetch next column's textual value */
if (attrs[i]->attisdropped)
{
values[i] = (Datum) 0;
nulls[i] = true;
continue;
}
/* convert value to internal representation */
if (PQgetisnull(res, row, j)) if (PQgetisnull(res, row, j))
{
valstr = NULL; valstr = NULL;
nulls[i] = true;
}
else else
{
valstr = PQgetvalue(res, row, j); valstr = PQgetvalue(res, row, j);
nulls[i] = false;
}
/* Note: apply the input function even to nulls, to support domains */
errpos.cur_attno = i + 1;
values[i] = InputFunctionCall(&attinmeta->attinfuncs[i],
valstr,
attinmeta->attioparams[i],
attinmeta->atttypmods[i]);
errpos.cur_attno = 0;
j++; /* convert value to internal representation */
} if (i > 0)
{
/* ordinary column */
Assert(i <= tupdesc->natts);
nulls[i - 1] = (valstr == NULL);
/* Apply the input function even to nulls, to support domains */
errpos.cur_attno = i;
values[i - 1] = InputFunctionCall(&attinmeta->attinfuncs[i - 1],
valstr,
attinmeta->attioparams[i - 1],
attinmeta->atttypmods[i - 1]);
errpos.cur_attno = 0;
}
else if (i == SelfItemPointerAttributeNumber)
{
/* ctid --- note we ignore any other system column in result */
if (valstr != NULL)
{
Datum datum;
/* datum = DirectFunctionCall1(tidin, CStringGetDatum(valstr));
* Convert ctid if present. XXX we could stand to have a cleaner way of ctid = (ItemPointer) DatumGetPointer(datum);
* detecting whether ctid is included in the result. }
*/ }
if (j < PQnfields(res))
{
char *valstr;
Datum datum;
valstr = PQgetvalue(res, row, j);
datum = DirectFunctionCall1(tidin, CStringGetDatum(valstr));
ctid = (ItemPointer) DatumGetPointer(datum);
j++; j++;
} }
/* Uninstall error context callback. */ /* Uninstall error context callback. */
error_context_stack = errcallback.previous; error_context_stack = errcallback.previous;
/* check result and tuple descriptor have the same number of columns */ /*
if (j != PQnfields(res)) * Check we got the expected number of columns. Note: j == 0 and
* PQnfields == 1 is expected, since deparse emits a NULL if no columns.
*/
if (j > 0 && j != PQnfields(res))
elog(ERROR, "remote query result does not match the foreign table"); elog(ERROR, "remote query result does not match the foreign table");
/* /*
......
...@@ -49,7 +49,8 @@ extern bool is_foreign_expr(PlannerInfo *root, ...@@ -49,7 +49,8 @@ extern bool is_foreign_expr(PlannerInfo *root,
extern void deparseSelectSql(StringInfo buf, extern void deparseSelectSql(StringInfo buf,
PlannerInfo *root, PlannerInfo *root,
RelOptInfo *baserel, RelOptInfo *baserel,
Bitmapset *attrs_used); Bitmapset *attrs_used,
List **retrieved_attrs);
extern void appendWhereClause(StringInfo buf, extern void appendWhereClause(StringInfo buf,
PlannerInfo *root, PlannerInfo *root,
RelOptInfo *baserel, RelOptInfo *baserel,
...@@ -58,14 +59,18 @@ extern void appendWhereClause(StringInfo buf, ...@@ -58,14 +59,18 @@ extern void appendWhereClause(StringInfo buf,
List **params); List **params);
extern void deparseInsertSql(StringInfo buf, PlannerInfo *root, extern void deparseInsertSql(StringInfo buf, PlannerInfo *root,
Index rtindex, Relation rel, Index rtindex, Relation rel,
List *targetAttrs, List *returningList); List *targetAttrs, List *returningList,
List **retrieved_attrs);
extern void deparseUpdateSql(StringInfo buf, PlannerInfo *root, 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);
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);
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);
#endif /* POSTGRES_FDW_H */ #endif /* POSTGRES_FDW_H */
...@@ -311,7 +311,9 @@ UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9', c7 = DEFAULT ...@@ -311,7 +311,9 @@ 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;
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;
DELETE FROM ft2 WHERE c1 % 10 = 5 RETURNING *; EXPLAIN (verbose, costs off)
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;
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;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment