pgsql-v9.3-writable-fdw-poc.v12.part-1.patch
application/octet-stream
Filename: pgsql-v9.3-writable-fdw-poc.v12.part-1.patch
Type: application/octet-stream
Part: 1
Message:
Re: [v9.3] writable foreign tables
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: unified
Series: patch v9
| File | + | − |
|---|---|---|
| doc/src/sgml/ddl.sgml | 0 | 5 |
| doc/src/sgml/fdwhandler.sgml | 169 | 1 |
| src/backend/executor/execMain.c | 30 | 4 |
| src/backend/executor/nodeForeignscan.c | 135 | 1 |
| src/backend/executor/nodeModifyTable.c | 139 | 24 |
| src/backend/foreign/foreign.c | 68 | 0 |
| src/backend/nodes/copyfuncs.c | 2 | 0 |
| src/backend/nodes/outfuncs.c | 2 | 0 |
| src/backend/optimizer/plan/createplan.c | 61 | 3 |
| src/backend/optimizer/plan/initsplan.c | 9 | 5 |
| src/backend/optimizer/plan/planmain.c | 4 | 1 |
| src/backend/optimizer/plan/planner.c | 9 | 33 |
| src/backend/optimizer/prep/prepunion.c | 2 | 1 |
| src/backend/optimizer/util/plancat.c | 26 | 1 |
| src/backend/optimizer/util/relnode.c | 4 | 3 |
| src/backend/parser/parse_relation.c | 14 | 1 |
| src/backend/rewrite/rewriteHandler.c | 45 | 10 |
| src/backend/utils/adt/ruleutils.c | 14 | 2 |
| src/include/foreign/fdwapi.h | 31 | 0 |
| src/include/foreign/foreign.h | 6 | 0 |
| src/include/nodes/execnodes.h | 10 | 3 |
| src/include/nodes/plannodes.h | 2 | 0 |
| src/include/optimizer/pathnode.h | 1 | 1 |
| src/include/optimizer/plancat.h | 1 | 1 |
| src/include/optimizer/planmain.h | 4 | 4 |
doc/src/sgml/ddl.sgml | 5 -
doc/src/sgml/fdwhandler.sgml | 170 +++++++++++++++++++++++++++++++-
src/backend/executor/execMain.c | 34 ++++++-
src/backend/executor/nodeForeignscan.c | 136 ++++++++++++++++++++++++-
src/backend/executor/nodeModifyTable.c | 163 +++++++++++++++++++++++++-----
src/backend/foreign/foreign.c | 68 +++++++++++++
src/backend/nodes/copyfuncs.c | 2 +
src/backend/nodes/outfuncs.c | 2 +
src/backend/optimizer/plan/createplan.c | 64 +++++++++++-
src/backend/optimizer/plan/initsplan.c | 14 ++-
src/backend/optimizer/plan/planmain.c | 5 +-
src/backend/optimizer/plan/planner.c | 42 ++------
src/backend/optimizer/prep/prepunion.c | 3 +-
src/backend/optimizer/util/plancat.c | 27 ++++-
src/backend/optimizer/util/relnode.c | 7 +-
src/backend/parser/parse_relation.c | 15 ++-
src/backend/rewrite/rewriteHandler.c | 55 +++++++++--
src/backend/utils/adt/ruleutils.c | 16 ++-
src/include/foreign/fdwapi.h | 31 ++++++
src/include/foreign/foreign.h | 6 ++
src/include/nodes/execnodes.h | 13 ++-
src/include/nodes/plannodes.h | 2 +
src/include/optimizer/pathnode.h | 2 +-
src/include/optimizer/plancat.h | 2 +-
src/include/optimizer/planmain.h | 8 +-
25 files changed, 788 insertions(+), 104 deletions(-)
diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml
index 207de9b..8f44d8d 100644
--- a/doc/src/sgml/ddl.sgml
+++ b/doc/src/sgml/ddl.sgml
@@ -3066,11 +3066,6 @@ ANALYZE measurement;
<firstterm>user mapping</>, which can provide additional options based
on the current <productname>PostgreSQL</productname> role.
</para>
-
- <para>
- Currently, foreign tables are read-only. This limitation may be fixed
- in a future release.
- </para>
</sect1>
<sect1 id="ddl-others">
diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml
index 638b6ab..731016f 100644
--- a/doc/src/sgml/fdwhandler.sgml
+++ b/doc/src/sgml/fdwhandler.sgml
@@ -89,6 +89,54 @@
<para>
<programlisting>
+AttrNumber
+GetForeignRelWidth(PlannerInfo *root,
+ RelOptInfo *baserel,
+ Relation foreignrel,
+ bool inhparent,
+ List *targetList);
+</programlisting>
+ Obtain the width of the result set to be fetched during a foreign table scan.
+ This is an optional handler, and called before <literal>GetForeignRelSize</>
+ for a query involving a foreign table
+ (during the construction of <literal>RelOptInfo</>).
+ <literal>root</> is the planner's global information about the query,
+ <literal>baserel</> is the planner's information being constructed for
+ this query, and <literal>foreignrel</> is a <literal>Relation</>
+ descriptor of the foreign table.
+ <literal>inhparent</> is a boolean to show whether the relation is
+ an inheritance parent, even though foreign tables do not support table
+ inheritance right now. <literal>targetList</> is the list of
+ <literal>TargetEntry</> to be returned from the (sub-)query
+ that is currently in focus.
+ </para>
+
+ <para>
+ The result value of this function will be assigned to
+ <literal>baserel->max_attr</>, that means it is the expected number
+ of columns being fetched during the foreign table scan.
+ It should not be smaller than the number of regular columns in the definition
+ of this foreign table. You can only return a number greater than his value to
+ acquire slots for some additional attributes, which are called
+ <firstterm>pseudo-columns</>.
+ A typical usage of a pseudo-column is to carry an identifier of
+ a particular remote row to be updated or deleted from the scanning stage
+ to the modifying stage when the foreign table is the target of
+ a data-modifying SQL statement.
+ You can return the result of the helper function
+ <literal>get_pseudo_rowid_column</> if this <literal>"rowid"</>
+ pseudo-column is the only one you need.
+ </para>
+
+ <para>
+ In addition to that, pseudo-columns can be used to off-load the burden of
+ complex calculations to foreign computing resources by replacing an
+ expression with a reference to its result, which is calculated on the
+ remote side rather than locally.
+ </para>
+
+ <para>
+<programlisting>
void
GetForeignRelSize (PlannerInfo *root,
RelOptInfo *baserel,
@@ -96,7 +144,8 @@ GetForeignRelSize (PlannerInfo *root,
</programlisting>
Obtain relation size estimates for a foreign table. This is called
- at the beginning of planning for a query involving a foreign table.
+ for a query involving a foreign table at the beginning of planning
+ or right after <literal>GetForeignRelWidth</>, if that callback is configured.
<literal>root</> is the planner's global information about the query;
<literal>baserel</> is the planner's information about this table; and
<literal>foreigntableid</> is the <structname>pg_class</> OID of the
@@ -315,6 +364,125 @@ AcquireSampleRowsFunc (Relation relation, int elevel,
</para>
<para>
+ If a FDW supports writable foreign tables, it should implement
+ some or all of the following callback functions depending on
+ the needs and capabilities of the FDW.
+ </para>
+
+ <para>
+<programlisting>
+List *
+PlanForeignModify(PlannerInfo *root,
+ ModifyTable *plan,
+ Index resultRelation,
+ Plan *subplan);
+</programlisting>
+ It allows FDW drivers to construct private information relevant to
+ the modification of the foreign table. This private information must have
+ the form of a <literal>List *</>, which will be delivered as
+ third argument to <literal>BeginForeignModify</> during the execution stage.
+ </para>
+
+ <para>
+ <literal>root</> is the planner's global information about the query.
+ <literal>plan</> is the master plan to modify the result relation according
+ to its command type. Please consider that <literal>ModifyTable</>
+ may have multiple result relations in a future revision, even though
+ currently there is no support for table inheritance on foreign tables.
+ <literal>resultRelation</> is an index for the result relation in the
+ range table entries, and <literal>subplan</> is the relevant scan plan;
+ that should be a <literal>ForeignScan</> for <literal>UPDATE</> or
+ <literal>DELETE</>, so the driver can access the private information of
+ the scan stage using this argument.
+ </para>
+
+ <para>
+<programlisting>
+void
+BeginForeignModify (ModifyTableState *mtstate,
+ ResultRelInfo *resultRelInfo,
+ List *fdw_private,
+ Plan *subplan,
+ int eflags);
+</programlisting>
+ It is invoked at beginning of foreign table modification, during
+ executor startup. This routine should perform any initialization
+ needed prior to the actual table modifications, but not start
+ modifying the actual tuples. (That should be done during each call of
+ <function>ExecForeignInsert</>, <function>ExecForeignUpdate</> or
+ <function>ExecForeignDelete</>.)
+ </para>
+
+ <para>
+ The <structfield>ri_fdw_state</> field of <structname>ResultRelInfo</>
+ is reserved to store any private information relevant to this foreign table,
+ so it should be initialized to contain the per-scan state.
+ Please consider that <literal>ModifyTableState</> may have multiple
+ result relations in a future revision, even though currently there is no
+ support for table inheritance on foreign tables. <literal>resultRelInfo</>
+ is the master information connected to this foreign table.
+ <literal>fdw_private</> is private information constructed in
+ <literal>PlanForeignModify</>, and <literal>subplan</> is the relevant
+ scan plan of this table modification.
+ </para>
+
+ <para>
+<programlisting>
+TupleTableSlot *
+ExecForeignInsert (ResultRelInfo *resultRelInfo,
+ TupleTableSlot *slot);
+</programlisting>
+ Insert the given tuple into backing storage on behalf of the foreign table.
+ The supplied slot shall hold a tuple being already formed according to
+ the definition of the relation, thus all the pseudo-columns are already
+ filtered out.
+ FDW driver can modify the tuple to be inserted (due to before-row-insert
+ triggers at remote side, for example). In this case, this routine can
+ return a modified slot that affects result of <literal>RETURNING</>,
+ or <literal>NULL</> that means this insertion was skipped. Elsewhere,
+ it usually returns the given slot, as is.
+ </para>
+
+ <para>
+<programlisting>
+bool
+ExecForeignDelete (ResultRelInfo *resultRelInfo,
+ const char *rowid);
+</programlisting>
+ Delete the tuple being identified with <literal>rowid</> from the backing
+ storage on behalf of the foreign table.
+ FDW driver can return a boolean that means whether this deletion was done
+ actually, or not.
+ </para>
+
+ <para>
+<programlisting>
+TupleTableSlot *
+ExecForeignUpdate (ResultRelInfo *resultRelInfo,
+ const char *rowid,
+ TupleTableSlot *slot);
+</programlisting>
+ Update the tuple being identified with <literal>rowid</> in the backing
+ storage on behalf of the foreign table with the given slot that shall
+ hold a newer version of tuple.
+ FDW driver can modify the tuple to be updated (due to before-row-update
+ triggers at remote side, for example). In this case, this routine can
+ return a modified slot that affects result of <literal>RETURNING</>,
+ or <literal>NULL</> that means this update was skipped. Elsewhere,
+ it usually returns the given slot, as is.
+ </para>
+
+ <para>
+<programlisting>
+void
+EndForeignModify (ResultRelInfo *resultRelInfo);
+</programlisting>
+ End the modification and release resources. It is normally not important
+ to release palloc'd memory, but for example open files and connections
+ to remote servers should be cleaned up.
+ </para>
+
+ <para>
The <structname>FdwRoutine</> struct type is declared in
<filename>src/include/foreign/fdwapi.h</>, which see for additional
details.
diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c
index 632644f..ce977ee 100644
--- a/src/backend/executor/execMain.c
+++ b/src/backend/executor/execMain.c
@@ -44,6 +44,7 @@
#include "catalog/namespace.h"
#include "commands/trigger.h"
#include "executor/execdebug.h"
+#include "foreign/fdwapi.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "optimizer/clauses.h"
@@ -938,6 +939,7 @@ void
CheckValidResultRel(Relation resultRel, CmdType operation)
{
TriggerDesc *trigDesc = resultRel->trigdesc;
+ FdwRoutine *fdwroutine;
switch (resultRel->rd_rel->relkind)
{
@@ -996,10 +998,34 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
}
break;
case RELKIND_FOREIGN_TABLE:
- ereport(ERROR,
- (errcode(ERRCODE_WRONG_OBJECT_TYPE),
- errmsg("cannot change foreign table \"%s\"",
- RelationGetRelationName(resultRel))));
+ fdwroutine = GetFdwRoutineByRelId(RelationGetRelid(resultRel));
+ switch (operation)
+ {
+ case CMD_INSERT:
+ if (!fdwroutine || !fdwroutine->ExecForeignInsert)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("cannot insert into foreign table \"%s\"",
+ RelationGetRelationName(resultRel))));
+ break;
+ case CMD_UPDATE:
+ if (!fdwroutine || !fdwroutine->ExecForeignUpdate)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("cannot update foreign table \"%s\"",
+ RelationGetRelationName(resultRel))));
+ break;
+ case CMD_DELETE:
+ if (!fdwroutine || !fdwroutine->ExecForeignDelete)
+ ereport(ERROR,
+ (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
+ errmsg("cannot delete from foreign table \"%s\"",
+ RelationGetRelationName(resultRel))));
+ break;
+ default:
+ elog(ERROR, "unrecognized CmdType: %d", (int) operation);
+ break;
+ }
break;
default:
ereport(ERROR,
diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c
index 6ebffad..4eaf2a9 100644
--- a/src/backend/executor/nodeForeignscan.c
+++ b/src/backend/executor/nodeForeignscan.c
@@ -25,6 +25,7 @@
#include "executor/executor.h"
#include "executor/nodeForeignscan.h"
#include "foreign/fdwapi.h"
+#include "nodes/nodeFuncs.h"
#include "utils/rel.h"
static TupleTableSlot *ForeignNext(ForeignScanState *node);
@@ -93,6 +94,133 @@ ExecForeignScan(ForeignScanState *node)
(ExecScanRecheckMtd) ForeignRecheck);
}
+/*
+ * pseudo_column_walker
+ *
+ * helper routine of GetPseudoTupleDesc. It pulls Var nodes that reference
+ * pseudo columns from targetlis of the relation
+ */
+typedef struct
+{
+ Relation relation;
+ Index varno;
+ List *pcolumns;
+ AttrNumber max_attno;
+} pseudo_column_walker_context;
+
+static bool
+pseudo_column_walker(Node *node, pseudo_column_walker_context *context)
+{
+ if (node == NULL)
+ return false;
+ if (IsA(node, Var))
+ {
+ Var *var = (Var *) node;
+ ListCell *cell;
+
+ if (var->varno == context->varno && var->varlevelsup == 0 &&
+ var->varattno > RelationGetNumberOfAttributes(context->relation))
+ {
+ foreach (cell, context->pcolumns)
+ {
+ Var *temp = lfirst(cell);
+
+ if (temp->varattno == var->varattno)
+ {
+ if (!equal(var, temp))
+ elog(ERROR, "asymmetric pseudo column appeared");
+ break;
+ }
+ }
+ if (!cell)
+ {
+ context->pcolumns = lappend(context->pcolumns, var);
+ if (var->varattno > context->max_attno)
+ context->max_attno = var->varattno;
+ }
+ }
+ return false;
+ }
+
+ /* Should not find an unplanned subquery */
+ Assert(!IsA(node, Query));
+
+ return expression_tree_walker(node, pseudo_column_walker,
+ (void *)context);
+}
+
+/*
+ * GetPseudoTupleDesc
+ *
+ * It generates TupleDesc structure including pseudo-columns if required.
+ */
+static TupleDesc
+GetPseudoTupleDesc(ForeignScan *node, Relation relation)
+{
+ pseudo_column_walker_context context;
+ List *target_list = node->scan.plan.targetlist;
+ TupleDesc tupdesc;
+ AttrNumber attno;
+ ListCell *cell;
+ ListCell *prev;
+ bool hasoid;
+
+ context.relation = relation;
+ context.varno = node->scan.scanrelid;
+ context.pcolumns = NIL;
+ context.max_attno = -1;
+
+ pseudo_column_walker((Node *)target_list, (void *)&context);
+ Assert(context.max_attno > RelationGetNumberOfAttributes(relation));
+
+ hasoid = RelationGetForm(relation)->relhasoids;
+ tupdesc = CreateTemplateTupleDesc(context.max_attno, hasoid);
+
+ for (attno = 1; attno <= context.max_attno; attno++)
+ {
+ /* case of regular columns */
+ if (attno <= RelationGetNumberOfAttributes(relation))
+ {
+ memcpy(tupdesc->attrs[attno - 1],
+ RelationGetDescr(relation)->attrs[attno - 1],
+ ATTRIBUTE_FIXED_PART_SIZE);
+ continue;
+ }
+
+ /* case of pseudo columns */
+ prev = NULL;
+ foreach (cell, context.pcolumns)
+ {
+ Var *var = lfirst(cell);
+
+ if (var->varattno == attno)
+ {
+ char namebuf[NAMEDATALEN];
+
+ snprintf(namebuf, sizeof(namebuf),
+ "pseudo_column_%d", attno);
+
+ TupleDescInitEntry(tupdesc,
+ attno,
+ namebuf,
+ var->vartype,
+ var->vartypmod,
+ 0);
+ TupleDescInitEntryCollation(tupdesc,
+ attno,
+ var->varcollid);
+ context.pcolumns
+ = list_delete_cell(context.pcolumns, cell, prev);
+ break;
+ }
+ prev = cell;
+ }
+ if (!cell)
+ elog(ERROR, "pseudo column %d of %s not in target list",
+ attno, RelationGetRelationName(relation));
+ }
+ return tupdesc;
+}
/* ----------------------------------------------------------------
* ExecInitForeignScan
@@ -103,6 +231,7 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
{
ForeignScanState *scanstate;
Relation currentRelation;
+ TupleDesc tupdesc;
FdwRoutine *fdwroutine;
/* check for unsupported flags */
@@ -149,7 +278,12 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
/*
* get the scan type from the relation descriptor.
*/
- ExecAssignScanType(&scanstate->ss, RelationGetDescr(currentRelation));
+ if (node->fsPseudoCol)
+ tupdesc = GetPseudoTupleDesc(node, currentRelation);
+ else
+ tupdesc = RelationGetDescr(currentRelation);
+
+ ExecAssignScanType(&scanstate->ss, tupdesc);
/*
* Initialize result tuple type and projection info.
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index cb084d0..9572b3a 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -42,6 +42,7 @@
#include "commands/trigger.h"
#include "executor/executor.h"
#include "executor/nodeModifyTable.h"
+#include "foreign/fdwapi.h"
#include "miscadmin.h"
#include "nodes/nodeFuncs.h"
#include "storage/bufmgr.h"
@@ -225,6 +226,20 @@ ExecInsert(TupleTableSlot *slot,
newId = InvalidOid;
}
+ else if (resultRelInfo->ri_fdwroutine)
+ {
+ FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+
+ slot = fdwroutine->ExecForeignInsert(resultRelInfo, slot);
+
+ if (slot == NULL) /* "do nothing" */
+ return NULL;
+
+ /* FDW driver might have changed tuple */
+ tuple = ExecMaterializeSlot(slot);
+
+ newId = InvalidOid;
+ }
else
{
/*
@@ -252,7 +267,7 @@ ExecInsert(TupleTableSlot *slot,
if (canSetTag)
{
- (estate->es_processed)++;
+ (estate->es_processed) ++;
estate->es_lastoid = newId;
setLastTid(&(tuple->t_self));
}
@@ -285,7 +300,7 @@ ExecInsert(TupleTableSlot *slot,
* ----------------------------------------------------------------
*/
static TupleTableSlot *
-ExecDelete(ItemPointer tupleid,
+ExecDelete(Datum rowid,
HeapTupleHeader oldtuple,
TupleTableSlot *planSlot,
EPQState *epqstate,
@@ -310,7 +325,7 @@ ExecDelete(ItemPointer tupleid,
bool dodelete;
dodelete = ExecBRDeleteTriggers(estate, epqstate, resultRelInfo,
- tupleid);
+ (ItemPointer)DatumGetPointer(rowid));
if (!dodelete) /* "do nothing" */
return NULL;
@@ -334,8 +349,20 @@ ExecDelete(ItemPointer tupleid,
if (!dodelete) /* "do nothing" */
return NULL;
}
+ else if (resultRelInfo->ri_fdwroutine)
+ {
+ FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+ bool dodelete;
+
+ dodelete = fdwroutine->ExecForeignDelete(resultRelInfo,
+ DatumGetCString(rowid));
+ if (!dodelete)
+ return NULL; /* "do nothing" */
+ }
else
{
+ ItemPointer tupleid = (ItemPointer) DatumGetPointer(rowid);
+
/*
* delete the tuple
*
@@ -431,10 +458,11 @@ ldelete:;
}
if (canSetTag)
- (estate->es_processed)++;
+ (estate->es_processed) ++;
/* AFTER ROW DELETE Triggers */
- ExecARDeleteTriggers(estate, resultRelInfo, tupleid);
+ ExecARDeleteTriggers(estate, resultRelInfo,
+ (ItemPointer)DatumGetPointer(rowid));
/* Process RETURNING if present */
if (resultRelInfo->ri_projectReturning)
@@ -458,7 +486,8 @@ ldelete:;
}
else
{
- deltuple.t_self = *tupleid;
+ ItemPointerCopy((ItemPointer)DatumGetPointer(rowid),
+ &deltuple.t_self);
if (!heap_fetch(resultRelationDesc, SnapshotAny,
&deltuple, &delbuffer, false, NULL))
elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING");
@@ -500,7 +529,7 @@ ldelete:;
* ----------------------------------------------------------------
*/
static TupleTableSlot *
-ExecUpdate(ItemPointer tupleid,
+ExecUpdate(Datum rowid,
HeapTupleHeader oldtuple,
TupleTableSlot *slot,
TupleTableSlot *planSlot,
@@ -538,7 +567,7 @@ ExecUpdate(ItemPointer tupleid,
resultRelInfo->ri_TrigDesc->trig_update_before_row)
{
slot = ExecBRUpdateTriggers(estate, epqstate, resultRelInfo,
- tupleid, slot);
+ (ItemPointer)DatumGetPointer(rowid), slot);
if (slot == NULL) /* "do nothing" */
return NULL;
@@ -568,9 +597,23 @@ ExecUpdate(ItemPointer tupleid,
/* trigger might have changed tuple */
tuple = ExecMaterializeSlot(slot);
}
+ else if (resultRelInfo->ri_fdwroutine)
+ {
+ FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+
+ slot = fdwroutine->ExecForeignUpdate(resultRelInfo,
+ DatumGetCString(rowid),
+ slot);
+ if (slot == NULL) /* "do nothing" */
+ return NULL;
+
+ /* FDW driver might have changed tuple */
+ tuple = ExecMaterializeSlot(slot);
+ }
else
{
LockTupleMode lockmode;
+ ItemPointer tupleid = (ItemPointer) DatumGetPointer(rowid);
/*
* Check the constraints of the tuple
@@ -691,11 +734,12 @@ lreplace:;
}
if (canSetTag)
- (estate->es_processed)++;
+ (estate->es_processed) ++;
/* AFTER ROW UPDATE Triggers */
- ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple,
- recheckIndexes);
+ ExecARUpdateTriggers(estate, resultRelInfo,
+ (ItemPointer) DatumGetPointer(rowid),
+ tuple, recheckIndexes);
list_free(recheckIndexes);
@@ -775,6 +819,7 @@ ExecModifyTable(ModifyTableState *node)
TupleTableSlot *planSlot;
ItemPointer tupleid = NULL;
ItemPointerData tuple_ctid;
+ Datum rowid = 0;
HeapTupleHeader oldtuple = NULL;
/*
@@ -863,17 +908,19 @@ ExecModifyTable(ModifyTableState *node)
if (junkfilter != NULL)
{
/*
- * extract the 'ctid' or 'wholerow' junk attribute.
+ * extract the 'ctid', 'rowid' or 'wholerow' junk attribute.
*/
if (operation == CMD_UPDATE || operation == CMD_DELETE)
{
+ char relkind;
Datum datum;
bool isNull;
- if (resultRelInfo->ri_RelationDesc->rd_rel->relkind == RELKIND_RELATION)
+ relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
+ if (relkind == RELKIND_RELATION)
{
datum = ExecGetJunkAttribute(slot,
- junkfilter->jf_junkAttNo,
+ junkfilter->jf_junkRowidNo,
&isNull);
/* shouldn't ever get a null result... */
if (isNull)
@@ -881,13 +928,33 @@ ExecModifyTable(ModifyTableState *node)
tupleid = (ItemPointer) DatumGetPointer(datum);
tuple_ctid = *tupleid; /* be sure we don't free
- * ctid!! */
- tupleid = &tuple_ctid;
+ * ctid ! */
+ rowid = PointerGetDatum(&tuple_ctid);
+ }
+ else if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ datum = ExecGetJunkAttribute(slot,
+ junkfilter->jf_junkRowidNo,
+ &isNull);
+ /* shouldn't ever get a null result... */
+ if (isNull)
+ elog(ERROR, "rowid is NULL");
+
+ rowid = datum;
+
+ datum = ExecGetJunkAttribute(slot,
+ junkfilter->jf_junkRecordNo,
+ &isNull);
+ /* shouldn't ever get a null result... */
+ if (isNull)
+ elog(ERROR, "wholerow is NULL");
+
+ oldtuple = DatumGetHeapTupleHeader(datum);
}
else
{
datum = ExecGetJunkAttribute(slot,
- junkfilter->jf_junkAttNo,
+ junkfilter->jf_junkRecordNo,
&isNull);
/* shouldn't ever get a null result... */
if (isNull)
@@ -910,11 +977,11 @@ ExecModifyTable(ModifyTableState *node)
slot = ExecInsert(slot, planSlot, estate, node->canSetTag);
break;
case CMD_UPDATE:
- slot = ExecUpdate(tupleid, oldtuple, slot, planSlot,
+ slot = ExecUpdate(rowid, oldtuple, slot, planSlot,
&node->mt_epqstate, estate, node->canSetTag);
break;
case CMD_DELETE:
- slot = ExecDelete(tupleid, oldtuple, planSlot,
+ slot = ExecDelete(rowid, oldtuple, planSlot,
&node->mt_epqstate, estate, node->canSetTag);
break;
default:
@@ -1001,6 +1068,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
i = 0;
foreach(l, node->plans)
{
+ char relkind;
+
subplan = (Plan *) lfirst(l);
/*
@@ -1026,6 +1095,24 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
estate->es_result_relation_info = resultRelInfo;
mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
+ /*
+ * Also tells FDW extensions to init the plan for this result rel
+ */
+ relkind = RelationGetForm(resultRelInfo->ri_RelationDesc)->relkind;
+ if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ Oid relid = RelationGetRelid(resultRelInfo->ri_RelationDesc);
+ FdwRoutine *fdwroutine = GetFdwRoutineByRelId(relid);
+ List *fdwprivate = list_nth(node->fdwPrivList, i);
+
+ Assert(fdwroutine != NULL);
+ resultRelInfo->ri_fdwroutine = fdwroutine;
+ resultRelInfo->ri_fdw_state = NULL;
+
+ if (fdwroutine->BeginForeignModify)
+ fdwroutine->BeginForeignModify(mtstate, resultRelInfo,
+ fdwprivate, subplan, eflags);
+ }
resultRelInfo++;
i++;
}
@@ -1167,6 +1254,8 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
for (i = 0; i < nplans; i++)
{
JunkFilter *j;
+ char relkind =
+ RelationGetForm(resultRelInfo->ri_RelationDesc)->relkind;
subplan = mtstate->mt_plans[i]->plan;
if (operation == CMD_INSERT || operation == CMD_UPDATE)
@@ -1180,16 +1269,27 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
if (operation == CMD_UPDATE || operation == CMD_DELETE)
{
/* For UPDATE/DELETE, find the appropriate junk attr now */
- if (resultRelInfo->ri_RelationDesc->rd_rel->relkind == RELKIND_RELATION)
+ if (relkind == RELKIND_RELATION)
{
- j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
- if (!AttributeNumberIsValid(j->jf_junkAttNo))
+ j->jf_junkRowidNo = ExecFindJunkAttribute(j, "ctid");
+ if (!AttributeNumberIsValid(j->jf_junkRowidNo))
elog(ERROR, "could not find junk ctid column");
}
+ else if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ j->jf_junkRowidNo = ExecFindJunkAttribute(j, "rowid");
+ if (!AttributeNumberIsValid(j->jf_junkRowidNo))
+ elog(ERROR, "could not find junk rowid column");
+ j->jf_junkRecordNo
+ = ExecFindJunkAttribute(j, "record");
+ if (!AttributeNumberIsValid(j->jf_junkRecordNo))
+ elog(ERROR, "could not find junk wholerow column");
+ }
else
{
- j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
- if (!AttributeNumberIsValid(j->jf_junkAttNo))
+ j->jf_junkRecordNo
+ = ExecFindJunkAttribute(j, "wholerow");
+ if (!AttributeNumberIsValid(j->jf_junkRecordNo))
elog(ERROR, "could not find junk wholerow column");
}
}
@@ -1243,6 +1343,21 @@ ExecEndModifyTable(ModifyTableState *node)
{
int i;
+ /* Let the FDW shut dowm */
+ for (i=0; i < node->ps.state->es_num_result_relations; i++)
+ {
+ ResultRelInfo *resultRelInfo
+ = &node->ps.state->es_result_relations[i];
+
+ if (resultRelInfo->ri_fdwroutine)
+ {
+ FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine;
+
+ if (fdwroutine->EndForeignModify)
+ fdwroutine->EndForeignModify(resultRelInfo);
+ }
+ }
+
/*
* Free the exprcontext
*/
diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c
index 872ed1f..cdeeb03 100644
--- a/src/backend/foreign/foreign.c
+++ b/src/backend/foreign/foreign.c
@@ -571,3 +571,71 @@ get_foreign_server_oid(const char *servername, bool missing_ok)
errmsg("server \"%s\" does not exist", servername)));
return oid;
}
+
+/*
+ * get_pseudo_rowid_column
+ *
+ * It picks up an attribute number to be used for the pseudo rowid column,
+ * if it exists. It should be injected at rewriteHandler.c if the supplied query
+ * is a UPDATE or DELETE command. Elsewhere, it returns InvalidAttrNumber.
+ */
+AttrNumber
+get_pseudo_rowid_column(RelOptInfo *baserel, List *targetList)
+{
+ ListCell *cell;
+
+ foreach (cell, targetList)
+ {
+ TargetEntry *tle = lfirst(cell);
+
+ if (tle->resjunk &&
+ tle->resname && strcmp(tle->resname, "rowid") == 0)
+ {
+ Var *var;
+
+ if (!IsA(tle->expr, Var))
+ elog(ERROR, "unexpected node on junk rowid entry: %d",
+ (int) nodeTag(tle->expr));
+
+ var = (Var *) tle->expr;
+ if (baserel->relid == var->varno)
+ return var->varattno;
+ }
+ }
+ return InvalidAttrNumber;
+}
+
+/*
+ * lookup_foreign_scan_plan
+ *
+ * It looks up the ForeignScan plan node with the supplied range-table id.
+ * Its typical usage is for PlanForeignModify to get the underlying scan plan on
+ * UPDATE or DELETE commands.
+ */
+ForeignScan *
+lookup_foreign_scan_plan(Plan *subplan, Index rtindex)
+{
+ if (!subplan)
+ return NULL;
+
+ else if (IsA(subplan, ForeignScan))
+ {
+ ForeignScan *fscan = (ForeignScan *) subplan;
+
+ if (fscan->scan.scanrelid == rtindex)
+ return fscan;
+ }
+ else
+ {
+ ForeignScan *fscan;
+
+ fscan = lookup_foreign_scan_plan(subplan->lefttree, rtindex);
+ if (fscan != NULL)
+ return fscan;
+
+ fscan = lookup_foreign_scan_plan(subplan->righttree, rtindex);
+ if (fscan != NULL)
+ return fscan;
+ }
+ return NULL;
+}
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 2da08d1..3593ddf 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -181,6 +181,7 @@ _copyModifyTable(const ModifyTable *from)
COPY_NODE_FIELD(returningLists);
COPY_NODE_FIELD(rowMarks);
COPY_SCALAR_FIELD(epqParam);
+ COPY_NODE_FIELD(fdwPrivList);
return newnode;
}
@@ -594,6 +595,7 @@ _copyForeignScan(const ForeignScan *from)
COPY_NODE_FIELD(fdw_exprs);
COPY_NODE_FIELD(fdw_private);
COPY_SCALAR_FIELD(fsSystemCol);
+ COPY_SCALAR_FIELD(fsPseudoCol);
return newnode;
}
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index ffd123d..6ab2dcd 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -335,6 +335,7 @@ _outModifyTable(StringInfo str, const ModifyTable *node)
WRITE_NODE_FIELD(returningLists);
WRITE_NODE_FIELD(rowMarks);
WRITE_INT_FIELD(epqParam);
+ WRITE_NODE_FIELD(fdwPrivList);
}
static void
@@ -562,6 +563,7 @@ _outForeignScan(StringInfo str, const ForeignScan *node)
WRITE_NODE_FIELD(fdw_exprs);
WRITE_NODE_FIELD(fdw_private);
WRITE_BOOL_FIELD(fsSystemCol);
+ WRITE_BOOL_FIELD(fsPseudoCol);
}
static void
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 8a51b56..32a6070 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -39,6 +39,7 @@
#include "parser/parse_clause.h"
#include "parser/parsetree.h"
#include "utils/lsyscache.h"
+#include "utils/rel.h"
static Plan *create_plan_recurse(PlannerInfo *root, Path *best_path);
@@ -1943,6 +1944,8 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
RelOptInfo *rel = best_path->path.parent;
Index scan_relid = rel->relid;
RangeTblEntry *rte;
+ Relation relation;
+ AttrNumber num_attrs;
int i;
/* it should be a base rel... */
@@ -2001,6 +2004,22 @@ create_foreignscan_plan(PlannerInfo *root, ForeignPath *best_path,
}
}
+ /*
+ * Also, detect whether any pseudo columns are requested from rel.
+ */
+ relation = heap_open(rte->relid, NoLock);
+ scan_plan->fsPseudoCol = false;
+ num_attrs = RelationGetNumberOfAttributes(relation);
+ for (i = num_attrs + 1; i <= rel->max_attr; i++)
+ {
+ if (!bms_is_empty(rel->attr_needed[i - rel->min_attr]))
+ {
+ scan_plan->fsPseudoCol = true;
+ break;
+ }
+ }
+ heap_close(relation, NoLock);
+
return scan_plan;
}
@@ -4695,15 +4714,30 @@ make_result(PlannerInfo *root,
* to make it look better sometime.
*/
ModifyTable *
-make_modifytable(CmdType operation, bool canSetTag,
+make_modifytable(PlannerInfo *root,
List *resultRelations,
- List *subplans, List *returningLists,
- List *rowMarks, int epqParam)
+ List *subplans, List *returningLists)
{
ModifyTable *node = makeNode(ModifyTable);
Plan *plan = &node->plan;
double total_size;
ListCell *subnode;
+ ListCell *resultRel;
+ List *fdw_priv_list = NIL;
+ CmdType operation = root->parse->commandType;
+ bool canSetTag = root->parse->canSetTag;
+ List *rowMarks;
+ int epqParam = SS_assign_special_param(root);
+
+ /*
+ * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node will
+ * have dealt with fetching non-locked marked rows, else we need
+ * to have ModifyTable do that.
+ */
+ if (root->parse->rowMarks)
+ rowMarks = NIL;
+ else
+ rowMarks = root->rowMarks;
Assert(list_length(resultRelations) == list_length(subplans));
Assert(returningLists == NIL ||
@@ -4746,6 +4780,30 @@ make_modifytable(CmdType operation, bool canSetTag,
node->rowMarks = rowMarks;
node->epqParam = epqParam;
+ /*
+ * Allow FDW driver to construct its private plan if the result relation
+ * is a foreign table.
+ */
+ forboth (resultRel, resultRelations, subnode, subplans)
+ {
+ RangeTblEntry *rte = rt_fetch(lfirst_int(resultRel),
+ root->parse->rtable);
+ List *fdw_private = NIL;
+ char relkind = get_rel_relkind(rte->relid);
+
+ if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ FdwRoutine *fdwroutine = GetFdwRoutineByRelId(rte->relid);
+
+ if (fdwroutine && fdwroutine->PlanForeignModify)
+ fdw_private = fdwroutine->PlanForeignModify(root, node,
+ lfirst_int(resultRel),
+ (Plan *) lfirst(subnode));
+ }
+ fdw_priv_list = lappend(fdw_priv_list, fdw_private);
+ }
+ node->fdwPrivList = fdw_priv_list;
+
return node;
}
diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c
index 84ca674..e4f8d9f 100644
--- a/src/backend/optimizer/plan/initsplan.c
+++ b/src/backend/optimizer/plan/initsplan.c
@@ -83,9 +83,13 @@ static void check_hashjoinable(RestrictInfo *restrictinfo);
* is the only place that should call build_simple_rel with reloptkind
* RELOPT_BASEREL. (Note: build_simple_rel recurses internally to build
* "other rel" RelOptInfos for the members of any appendrels we find here.)
+ *
+ * XXX - Also note that tlist needs to be pushed down into deeper level,
+ * for construction of RelOptInfo relevant to foreign-tables with pseudo-
+ * columns.
*/
void
-add_base_rels_to_query(PlannerInfo *root, Node *jtnode)
+add_base_rels_to_query(PlannerInfo *root, List *tlist, Node *jtnode)
{
if (jtnode == NULL)
return;
@@ -93,7 +97,7 @@ add_base_rels_to_query(PlannerInfo *root, Node *jtnode)
{
int varno = ((RangeTblRef *) jtnode)->rtindex;
- (void) build_simple_rel(root, varno, RELOPT_BASEREL);
+ (void) build_simple_rel(root, varno, tlist, RELOPT_BASEREL);
}
else if (IsA(jtnode, FromExpr))
{
@@ -101,14 +105,14 @@ add_base_rels_to_query(PlannerInfo *root, Node *jtnode)
ListCell *l;
foreach(l, f->fromlist)
- add_base_rels_to_query(root, lfirst(l));
+ add_base_rels_to_query(root, tlist, lfirst(l));
}
else if (IsA(jtnode, JoinExpr))
{
JoinExpr *j = (JoinExpr *) jtnode;
- add_base_rels_to_query(root, j->larg);
- add_base_rels_to_query(root, j->rarg);
+ add_base_rels_to_query(root, tlist, j->larg);
+ add_base_rels_to_query(root, tlist, j->rarg);
}
else
elog(ERROR, "unrecognized node type: %d",
diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c
index a919914..0f26f6f 100644
--- a/src/backend/optimizer/plan/planmain.c
+++ b/src/backend/optimizer/plan/planmain.c
@@ -112,6 +112,9 @@ query_planner(PlannerInfo *root, List *tlist,
*/
if (parse->jointree->fromlist == NIL)
{
+ /* Make a flattened version of the rangetable for faster access */
+ setup_simple_rel_arrays(root);
+
/* We need a trivial path result */
*cheapest_path = (Path *)
create_result_path((List *) parse->jointree->quals);
@@ -163,7 +166,7 @@ query_planner(PlannerInfo *root, List *tlist,
* rangetable may contain RTEs for rels not actively part of the query,
* for example views. We don't want to make RelOptInfos for them.
*/
- add_base_rels_to_query(root, (Node *) parse->jointree);
+ add_base_rels_to_query(root, tlist, (Node *) parse->jointree);
/*
* Examine the targetlist and join tree, adding entries to baserel
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 5b97cb5..624644f 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -551,7 +551,6 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
if (parse->commandType != CMD_SELECT)
{
List *returningLists;
- List *rowMarks;
/*
* Set up the RETURNING list-of-lists, if needed.
@@ -561,23 +560,12 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
else
returningLists = NIL;
- /*
- * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node will
- * have dealt with fetching non-locked marked rows, else we need
- * to have ModifyTable do that.
- */
- if (parse->rowMarks)
- rowMarks = NIL;
- else
- rowMarks = root->rowMarks;
-
- plan = (Plan *) make_modifytable(parse->commandType,
- parse->canSetTag,
+ Assert(parse->commandType == root->parse->commandType);
+ Assert(parse->canSetTag == root->parse->canSetTag);
+ plan = (Plan *) make_modifytable(root,
list_make1_int(parse->resultRelation),
list_make1(plan),
- returningLists,
- rowMarks,
- SS_assign_special_param(root));
+ returningLists);
}
}
@@ -761,7 +749,6 @@ inheritance_planner(PlannerInfo *root)
List *subplans = NIL;
List *resultRelations = NIL;
List *returningLists = NIL;
- List *rowMarks;
ListCell *lc;
/*
@@ -953,24 +940,13 @@ inheritance_planner(PlannerInfo *root)
root->simple_rel_array_size = save_rel_array_size;
root->simple_rel_array = save_rel_array;
- /*
- * If there was a FOR [KEY] UPDATE/SHARE clause, the LockRows node will have
- * dealt with fetching non-locked marked rows, else we need to have
- * ModifyTable do that.
- */
- if (parse->rowMarks)
- rowMarks = NIL;
- else
- rowMarks = root->rowMarks;
-
/* And last, tack on a ModifyTable node to do the UPDATE/DELETE work */
- return (Plan *) make_modifytable(parse->commandType,
- parse->canSetTag,
+ Assert(parse->commandType == root->parse->commandType);
+ Assert(parse->canSetTag == root->parse->canSetTag);
+ return (Plan *) make_modifytable(root,
resultRelations,
subplans,
- returningLists,
- rowMarks,
- SS_assign_special_param(root));
+ returningLists);
}
/*--------------------
@@ -3396,7 +3372,7 @@ plan_cluster_use_sort(Oid tableOid, Oid indexOid)
setup_simple_rel_arrays(root);
/* Build RelOptInfo */
- rel = build_simple_rel(root, 1, RELOPT_BASEREL);
+ rel = build_simple_rel(root, 1, NIL, RELOPT_BASEREL);
/* Locate IndexOptInfo for the target index */
indexInfo = NULL;
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index e249628..e2984a5 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -236,7 +236,8 @@ recurse_set_operations(Node *setOp, PlannerInfo *root,
* used for anything here, but it carries the subroot data structures
* forward to setrefs.c processing.
*/
- rel = build_simple_rel(root, rtr->rtindex, RELOPT_BASEREL);
+ rel = build_simple_rel(root, rtr->rtindex, refnames_tlist,
+ RELOPT_BASEREL);
/* plan_params should not be in use in current query level */
Assert(root->plan_params == NIL);
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 01f988f..7e98cea 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -26,6 +26,7 @@
#include "access/xlog.h"
#include "catalog/catalog.h"
#include "catalog/heap.h"
+#include "foreign/fdwapi.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "optimizer/clauses.h"
@@ -81,7 +82,7 @@ static List *build_index_tlist(PlannerInfo *root, IndexOptInfo *index,
*/
void
get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
- RelOptInfo *rel)
+ List *tlist, RelOptInfo *rel)
{
Index varno = rel->relid;
Relation relation;
@@ -105,6 +106,30 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
rel->max_attr = RelationGetNumberOfAttributes(relation);
rel->reltablespace = RelationGetForm(relation)->reltablespace;
+ /*
+ * Adjust width of attr_needed slot in case FDW extension wants
+ * to return pseudo-columns in addition to the columns in its
+ * table definition.
+ * GetForeignRelWidth, an optional FDW handler, enables a FDW
+ * to save properties of a pseudo-column in its private field.
+ * When the foreign table is the target of UPDATE/DELETE, the query rewriter
+ * injects a "rowid" pseudo-column to track the remote row to be modified,
+ * so the FDW has to track which varattno shall perform as "rowid".
+ */
+ if (RelationGetForm(relation)->relkind == RELKIND_FOREIGN_TABLE)
+ {
+ FdwRoutine *fdwroutine = GetFdwRoutineByRelId(relationObjectId);
+
+ if (fdwroutine->GetForeignRelWidth)
+ {
+ rel->max_attr = fdwroutine->GetForeignRelWidth(root, rel,
+ relation,
+ inhparent,
+ tlist);
+ Assert(rel->max_attr >= RelationGetNumberOfAttributes(relation));
+ }
+ }
+
Assert(rel->max_attr >= rel->min_attr);
rel->attr_needed = (Relids *)
palloc0((rel->max_attr - rel->min_attr + 1) * sizeof(Relids));
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 8ee5671..ababef5 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -80,7 +80,8 @@ setup_simple_rel_arrays(PlannerInfo *root)
* Construct a new RelOptInfo for a base relation or 'other' relation.
*/
RelOptInfo *
-build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
+build_simple_rel(PlannerInfo *root, int relid, List *tlist,
+ RelOptKind reloptkind)
{
RelOptInfo *rel;
RangeTblEntry *rte;
@@ -133,7 +134,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
{
case RTE_RELATION:
/* Table --- retrieve statistics from the system catalogs */
- get_relation_info(root, rte->relid, rte->inh, rel);
+ get_relation_info(root, rte->relid, rte->inh, tlist, rel);
break;
case RTE_SUBQUERY:
case RTE_FUNCTION:
@@ -180,7 +181,7 @@ build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind)
if (appinfo->parent_relid != relid)
continue;
- (void) build_simple_rel(root, appinfo->child_relid,
+ (void) build_simple_rel(root, appinfo->child_relid, tlist,
RELOPT_OTHER_MEMBER_REL);
}
}
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index 93aeab8..f246d43 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -2087,7 +2087,20 @@ get_rte_attribute_name(RangeTblEntry *rte, AttrNumber attnum)
* built (which can easily happen for rules).
*/
if (rte->rtekind == RTE_RELATION)
- return get_relid_attribute_name(rte->relid, attnum);
+ {
+ char *attname = get_attname(rte->relid, attnum);
+
+ if (attname)
+ return attname;
+
+ /*
+ * XXX - If FDW driver adds pseudo-columns, it may have attribute
+ * number larger than number of relation's attribute. In this case,
+ * get_attname() returns NULL and we fall back on alias list on eref.
+ * It should not happen other than foreign tables.
+ */
+ Assert(get_rel_relkind(rte->relid) == RELKIND_FOREIGN_TABLE);
+ }
/*
* Otherwise use the column name from eref. There should always be one.
diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c
index b458de6..f6494d8 100644
--- a/src/backend/rewrite/rewriteHandler.c
+++ b/src/backend/rewrite/rewriteHandler.c
@@ -1165,7 +1165,10 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
Relation target_relation)
{
Var *var;
- const char *attrname;
+ List *varList;
+ List *attNameList;
+ ListCell *cell1;
+ ListCell *cell2;
TargetEntry *tle;
if (target_relation->rd_rel->relkind == RELKIND_RELATION)
@@ -1179,8 +1182,35 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
-1,
InvalidOid,
0);
+ varList = list_make1(var);
+ attNameList = list_make1("ctid");
+ }
+ else if (target_relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
+ {
+ /*
+ * Emit Rowid so that executor can find the row to update or delete.
+ */
+ var = makeVar(parsetree->resultRelation,
+ RelationGetNumberOfAttributes(target_relation) + 1,
+ CSTRINGOID,
+ -2,
+ InvalidOid,
+ 0);
+ varList = list_make1(var);
+
+ /*
+ * Emit generic record Var so that executor will have the "old" view
+ * row to pass the RETURNING clause (or upcoming triggers).
+ */
+ var = makeVar(parsetree->resultRelation,
+ InvalidAttrNumber,
+ RECORDOID,
+ -1,
+ InvalidOid,
+ 0);
+ varList = lappend(varList, var);
- attrname = "ctid";
+ attNameList = list_make2("rowid", "record");
}
else
{
@@ -1192,16 +1222,21 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
parsetree->resultRelation,
0,
false);
-
- attrname = "wholerow";
+ varList = list_make1(var);
+ attNameList = list_make1("wholerow");
}
- tle = makeTargetEntry((Expr *) var,
- list_length(parsetree->targetList) + 1,
- pstrdup(attrname),
- true);
-
- parsetree->targetList = lappend(parsetree->targetList, tle);
+ /*
+ * Append them to targetList
+ */
+ forboth (cell1, varList, cell2, attNameList)
+ {
+ tle = makeTargetEntry((Expr *)lfirst(cell1),
+ list_length(parsetree->targetList) + 1,
+ pstrdup((const char *)lfirst(cell2)),
+ true);
+ parsetree->targetList = lappend(parsetree->targetList, tle);
+ }
}
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index af04b05..31b211a 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -2872,16 +2872,28 @@ set_relation_column_names(deparse_namespace *dpns, RangeTblEntry *rte,
rel = relation_open(rte->relid, AccessShareLock);
tupdesc = RelationGetDescr(rel);
- ncolumns = tupdesc->natts;
+ ncolumns = (tupdesc->natts < list_length(rte->eref->colnames)
+ ? list_length(rte->eref->colnames)
+ : tupdesc->natts);
real_colnames = (char **) palloc(ncolumns * sizeof(char *));
- for (i = 0; i < ncolumns; i++)
+ for (i = 0; i < tupdesc->natts; i++)
{
if (tupdesc->attrs[i]->attisdropped)
real_colnames[i] = NULL;
else
real_colnames[i] = pstrdup(NameStr(tupdesc->attrs[i]->attname));
}
+ /*
+ * XXX - foreign table may have pseudo-column that has attribute
+ * number larger than or equal with number of attributes in table
+ * definition. So, we need to add entries for them.
+ */
+ while (i < ncolumns)
+ {
+ real_colnames[i] = strVal(list_nth(rte->eref->colnames, i));
+ i++;
+ }
relation_close(rel, AccessShareLock);
}
else
diff --git a/src/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h
index 13dcbfd..fd99591 100644
--- a/src/include/foreign/fdwapi.h
+++ b/src/include/foreign/fdwapi.h
@@ -14,6 +14,7 @@
#include "nodes/execnodes.h"
#include "nodes/relation.h"
+#include "utils/rel.h"
/* To avoid including explain.h here, reference ExplainState thus: */
struct ExplainState;
@@ -22,6 +23,11 @@ struct ExplainState;
/*
* Callback function signatures --- see fdwhandler.sgml for more info.
*/
+typedef AttrNumber (*GetForeignRelWidth_function) (PlannerInfo *root,
+ RelOptInfo *baserel,
+ Relation foreignrel,
+ bool inhparent,
+ List *targetList);
typedef void (*GetForeignRelSize_function) (PlannerInfo *root,
RelOptInfo *baserel,
@@ -58,6 +64,24 @@ typedef int (*AcquireSampleRowsFunc) (Relation relation, int elevel,
typedef bool (*AnalyzeForeignTable_function) (Relation relation,
AcquireSampleRowsFunc *func,
BlockNumber *totalpages);
+typedef List *(*PlanForeignModify_function) (PlannerInfo *root,
+ ModifyTable *plan,
+ Index resultRelation,
+ Plan *subplan);
+
+typedef void (*BeginForeignModify_function) (ModifyTableState *mtstate,
+ ResultRelInfo *rinfo,
+ List *fdw_private,
+ Plan *subplan,
+ int eflags);
+typedef TupleTableSlot *(*ExecForeignInsert_function) (ResultRelInfo *rinfo,
+ TupleTableSlot *slot);
+typedef TupleTableSlot *(*ExecForeignUpdate_function) (ResultRelInfo *rinfo,
+ const char *rowid,
+ TupleTableSlot *slot);
+typedef bool (*ExecForeignDelete_function) (ResultRelInfo *rinfo,
+ const char *rowid);
+typedef void (*EndForeignModify_function) (ResultRelInfo *rinfo);
/*
* FdwRoutine is the struct returned by a foreign-data wrapper's handler
@@ -90,6 +114,13 @@ typedef struct FdwRoutine
* not provided.
*/
AnalyzeForeignTable_function AnalyzeForeignTable;
+ GetForeignRelWidth_function GetForeignRelWidth;
+ PlanForeignModify_function PlanForeignModify;
+ BeginForeignModify_function BeginForeignModify;
+ ExecForeignInsert_function ExecForeignInsert;
+ ExecForeignDelete_function ExecForeignDelete;
+ ExecForeignUpdate_function ExecForeignUpdate;
+ EndForeignModify_function EndForeignModify;
} FdwRoutine;
diff --git a/src/include/foreign/foreign.h b/src/include/foreign/foreign.h
index 5bd6ae6..749f9a6 100644
--- a/src/include/foreign/foreign.h
+++ b/src/include/foreign/foreign.h
@@ -14,6 +14,8 @@
#define FOREIGN_H
#include "nodes/parsenodes.h"
+#include "nodes/plannodes.h"
+#include "nodes/relation.h"
/* Helper for obtaining username for user mapping */
@@ -81,4 +83,8 @@ extern List *GetForeignColumnOptions(Oid relid, AttrNumber attnum);
extern Oid get_foreign_data_wrapper_oid(const char *fdwname, bool missing_ok);
extern Oid get_foreign_server_oid(const char *servername, bool missing_ok);
+extern AttrNumber get_pseudo_rowid_column(RelOptInfo *baserel,
+ List *targetList);
+extern ForeignScan *lookup_foreign_scan_plan(Plan *subplan,
+ Index rtindex);
#endif /* FOREIGN_H */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 76e8cdb..97c2045 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -268,9 +268,11 @@ typedef struct ProjectionInfo
* attribute numbers of the "original" tuple and the
* attribute numbers of the "clean" tuple.
* resultSlot: tuple slot used to hold cleaned tuple.
- * junkAttNo: not used by junkfilter code. Can be used by caller
- * to remember the attno of a specific junk attribute
+ * jf_junkRowidNo: not used by junkfilter code. Can be used by caller
+ * to remember the attno used to track a particular tuple
+ * being updated or deleted.
* (execMain.c stores the "ctid" attno here).
+ * jf_junkRecordNo: Also, the attno of whole-row reference.
* ----------------
*/
typedef struct JunkFilter
@@ -280,7 +282,8 @@ typedef struct JunkFilter
TupleDesc jf_cleanTupType;
AttrNumber *jf_cleanMap;
TupleTableSlot *jf_resultSlot;
- AttrNumber jf_junkAttNo;
+ AttrNumber jf_junkRowidNo;
+ AttrNumber jf_junkRecordNo;
} JunkFilter;
/* ----------------
@@ -303,6 +306,8 @@ typedef struct JunkFilter
* ConstraintExprs array of constraint-checking expr states
* junkFilter for removing junk attributes from tuples
* projectReturning for computing a RETURNING list
+ * fdwroutine FDW callbacks if foreign table
+ * fdw_state opaque state of FDW module, or NULL
* ----------------
*/
typedef struct ResultRelInfo
@@ -320,6 +325,8 @@ typedef struct ResultRelInfo
List **ri_ConstraintExprs;
JunkFilter *ri_junkFilter;
ProjectionInfo *ri_projectReturning;
+ struct FdwRoutine *ri_fdwroutine;
+ void *ri_fdw_state;
} ResultRelInfo;
/* ----------------
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 0b8b107..1fd907d 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -175,6 +175,7 @@ typedef struct ModifyTable
List *returningLists; /* per-target-table RETURNING tlists */
List *rowMarks; /* PlanRowMarks (non-locking only) */
int epqParam; /* ID of Param for EvalPlanQual re-eval */
+ List *fdwPrivList; /* private fields for foreign tables */
} ModifyTable;
/* ----------------
@@ -478,6 +479,7 @@ typedef struct ForeignScan
List *fdw_exprs; /* expressions that FDW may evaluate */
List *fdw_private; /* private data for FDW */
bool fsSystemCol; /* true if any "system column" is needed */
+ bool fsPseudoCol; /* true if any "pseudo column" is needed */
} ForeignScan;
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index bc68789..91b2983 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -133,7 +133,7 @@ extern Path *reparameterize_path(PlannerInfo *root, Path *path,
*/
extern void setup_simple_rel_arrays(PlannerInfo *root);
extern RelOptInfo *build_simple_rel(PlannerInfo *root, int relid,
- RelOptKind reloptkind);
+ List *tlist, RelOptKind reloptkind);
extern RelOptInfo *find_base_rel(PlannerInfo *root, int relid);
extern RelOptInfo *find_join_rel(PlannerInfo *root, Relids relids);
extern RelOptInfo *build_join_rel(PlannerInfo *root,
diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h
index 6abd92a..59f175d 100644
--- a/src/include/optimizer/plancat.h
+++ b/src/include/optimizer/plancat.h
@@ -26,7 +26,7 @@ extern PGDLLIMPORT get_relation_info_hook_type get_relation_info_hook;
extern void get_relation_info(PlannerInfo *root, Oid relationObjectId,
- bool inhparent, RelOptInfo *rel);
+ bool inhparent, List *tlist, RelOptInfo *rel);
extern void estimate_rel_size(Relation rel, int32 *attr_widths,
BlockNumber *pages, double *tuples, double *allvisfrac);
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 2f9fcd5..b866103 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -79,9 +79,8 @@ extern SetOp *make_setop(SetOpCmd cmd, SetOpStrategy strategy, Plan *lefttree,
long numGroups, double outputRows);
extern Result *make_result(PlannerInfo *root, List *tlist,
Node *resconstantqual, Plan *subplan);
-extern ModifyTable *make_modifytable(CmdType operation, bool canSetTag,
- List *resultRelations, List *subplans, List *returningLists,
- List *rowMarks, int epqParam);
+extern ModifyTable *make_modifytable(PlannerInfo *root,
+ List *resultRelations, List *subplans, List *returningLists);
extern bool is_projection_capable_plan(Plan *plan);
/*
@@ -90,7 +89,8 @@ extern bool is_projection_capable_plan(Plan *plan);
extern int from_collapse_limit;
extern int join_collapse_limit;
-extern void add_base_rels_to_query(PlannerInfo *root, Node *jtnode);
+extern void add_base_rels_to_query(PlannerInfo *root, List *tlist,
+ Node *jtnode);
extern void build_base_rel_tlists(PlannerInfo *root, List *final_tlist);
extern void add_vars_to_targetlist(PlannerInfo *root, List *vars,
Relids where_needed, bool create_new_ph);