aboutsummaryrefslogtreecommitdiff
path: root/src/backend/executor/nodeModifyTable.c
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2013-03-10 14:14:53 -0400
committerTom Lane <tgl@sss.pgh.pa.us>2013-03-10 14:16:02 -0400
commit21734d2fb896e0ecdddd3251caa72a3576e2d415 (patch)
treeaed4ee5509e618c0fd9746c8be17c5bf23a08a3f /src/backend/executor/nodeModifyTable.c
parent7f49a67f954db3e92fd96963169fb8302959576e (diff)
downloadpostgresql-21734d2fb896e0ecdddd3251caa72a3576e2d415.tar.gz
postgresql-21734d2fb896e0ecdddd3251caa72a3576e2d415.zip
Support writable foreign tables.
This patch adds the core-system infrastructure needed to support updates on foreign tables, and extends contrib/postgres_fdw to allow updates against remote Postgres servers. There's still a great deal of room for improvement in optimization of remote updates, but at least there's basic functionality there now. KaiGai Kohei, reviewed by Alexander Korotkov and Laurenz Albe, and rather heavily revised by Tom Lane.
Diffstat (limited to 'src/backend/executor/nodeModifyTable.c')
-rw-r--r--src/backend/executor/nodeModifyTable.c151
1 files changed, 133 insertions, 18 deletions
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index cb084d03d47..a6f247e1bc3 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,24 @@ ExecInsert(TupleTableSlot *slot,
newId = InvalidOid;
}
+ else if (resultRelInfo->ri_FdwRoutine)
+ {
+ /*
+ * insert into foreign table: let the FDW do it
+ */
+ slot = resultRelInfo->ri_FdwRoutine->ExecForeignInsert(estate,
+ resultRelInfo,
+ slot,
+ planSlot);
+
+ if (slot == NULL) /* "do nothing" */
+ return NULL;
+
+ /* FDW might have changed tuple */
+ tuple = ExecMaterializeSlot(slot);
+
+ newId = InvalidOid;
+ }
else
{
/*
@@ -279,7 +298,9 @@ ExecInsert(TupleTableSlot *slot,
* When deleting from a table, tupleid identifies the tuple to
* delete and oldtuple is NULL. When deleting from a view,
* oldtuple is passed to the INSTEAD OF triggers and identifies
- * what to delete, and tupleid is invalid.
+ * what to delete, and tupleid is invalid. When deleting from a
+ * foreign table, both tupleid and oldtuple are NULL; the FDW has
+ * to figure out which row to delete using data from the planSlot.
*
* Returns RETURNING result if any, otherwise NULL.
* ----------------------------------------------------------------
@@ -296,6 +317,7 @@ ExecDelete(ItemPointer tupleid,
Relation resultRelationDesc;
HTSU_Result result;
HeapUpdateFailureData hufd;
+ TupleTableSlot *slot = NULL;
/*
* get information on the (current) result relation
@@ -334,6 +356,27 @@ ExecDelete(ItemPointer tupleid,
if (!dodelete) /* "do nothing" */
return NULL;
}
+ else if (resultRelInfo->ri_FdwRoutine)
+ {
+ /*
+ * delete from foreign table: let the FDW do it
+ *
+ * We offer the trigger tuple slot as a place to store RETURNING data,
+ * although the FDW can return some other slot if it wants. Set up
+ * the slot's tupdesc so the FDW doesn't need to do that for itself.
+ */
+ slot = estate->es_trig_tuple_slot;
+ if (slot->tts_tupleDescriptor != RelationGetDescr(resultRelationDesc))
+ ExecSetSlotDescriptor(slot, RelationGetDescr(resultRelationDesc));
+
+ slot = resultRelInfo->ri_FdwRoutine->ExecForeignDelete(estate,
+ resultRelInfo,
+ slot,
+ planSlot);
+
+ if (slot == NULL) /* "do nothing" */
+ return NULL;
+ }
else
{
/*
@@ -443,34 +486,49 @@ ldelete:;
* We have to put the target tuple into a slot, which means first we
* gotta fetch it. We can use the trigger tuple slot.
*/
- TupleTableSlot *slot = estate->es_trig_tuple_slot;
TupleTableSlot *rslot;
HeapTupleData deltuple;
Buffer delbuffer;
- if (oldtuple != NULL)
+ if (resultRelInfo->ri_FdwRoutine)
{
- deltuple.t_data = oldtuple;
- deltuple.t_len = HeapTupleHeaderGetDatumLength(oldtuple);
- ItemPointerSetInvalid(&(deltuple.t_self));
- deltuple.t_tableOid = InvalidOid;
+ /* FDW must have provided a slot containing the deleted row */
+ Assert(!TupIsNull(slot));
delbuffer = InvalidBuffer;
}
else
{
- deltuple.t_self = *tupleid;
- if (!heap_fetch(resultRelationDesc, SnapshotAny,
- &deltuple, &delbuffer, false, NULL))
- elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING");
- }
+ slot = estate->es_trig_tuple_slot;
+ if (oldtuple != NULL)
+ {
+ deltuple.t_data = oldtuple;
+ deltuple.t_len = HeapTupleHeaderGetDatumLength(oldtuple);
+ ItemPointerSetInvalid(&(deltuple.t_self));
+ deltuple.t_tableOid = InvalidOid;
+ delbuffer = InvalidBuffer;
+ }
+ else
+ {
+ deltuple.t_self = *tupleid;
+ if (!heap_fetch(resultRelationDesc, SnapshotAny,
+ &deltuple, &delbuffer, false, NULL))
+ elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING");
+ }
- if (slot->tts_tupleDescriptor != RelationGetDescr(resultRelationDesc))
- ExecSetSlotDescriptor(slot, RelationGetDescr(resultRelationDesc));
- ExecStoreTuple(&deltuple, slot, InvalidBuffer, false);
+ if (slot->tts_tupleDescriptor != RelationGetDescr(resultRelationDesc))
+ ExecSetSlotDescriptor(slot, RelationGetDescr(resultRelationDesc));
+ ExecStoreTuple(&deltuple, slot, InvalidBuffer, false);
+ }
rslot = ExecProcessReturning(resultRelInfo->ri_projectReturning,
slot, planSlot);
+ /*
+ * Before releasing the target tuple again, make sure rslot has a
+ * local copy of any pass-by-reference values.
+ */
+ ExecMaterializeSlot(rslot);
+
ExecClearTuple(slot);
if (BufferIsValid(delbuffer))
ReleaseBuffer(delbuffer);
@@ -494,7 +552,9 @@ ldelete:;
* When updating a table, tupleid identifies the tuple to
* update and oldtuple is NULL. When updating a view, oldtuple
* is passed to the INSTEAD OF triggers and identifies what to
- * update, and tupleid is invalid.
+ * update, and tupleid is invalid. When updating a foreign table,
+ * both tupleid and oldtuple are NULL; the FDW has to figure out
+ * which row to update using data from the planSlot.
*
* Returns RETURNING result if any, otherwise NULL.
* ----------------------------------------------------------------
@@ -568,6 +628,22 @@ ExecUpdate(ItemPointer tupleid,
/* trigger might have changed tuple */
tuple = ExecMaterializeSlot(slot);
}
+ else if (resultRelInfo->ri_FdwRoutine)
+ {
+ /*
+ * update in foreign table: let the FDW do it
+ */
+ slot = resultRelInfo->ri_FdwRoutine->ExecForeignUpdate(estate,
+ resultRelInfo,
+ slot,
+ planSlot);
+
+ if (slot == NULL) /* "do nothing" */
+ return NULL;
+
+ /* FDW might have changed tuple */
+ tuple = ExecMaterializeSlot(slot);
+ }
else
{
LockTupleMode lockmode;
@@ -867,10 +943,12 @@ ExecModifyTable(ModifyTableState *node)
*/
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,
@@ -884,6 +962,10 @@ ExecModifyTable(ModifyTableState *node)
* ctid!! */
tupleid = &tuple_ctid;
}
+ else if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ /* do nothing; FDW must fetch any junk attrs it wants */
+ }
else
{
datum = ExecGetJunkAttribute(slot,
@@ -1026,6 +1108,19 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags)
estate->es_result_relation_info = resultRelInfo;
mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags);
+ /* Also let FDWs init themselves for foreign-table result rels */
+ if (resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->BeginForeignModify != NULL)
+ {
+ List *fdw_private = (List *) list_nth(node->fdwPrivLists, i);
+
+ resultRelInfo->ri_FdwRoutine->BeginForeignModify(mtstate,
+ resultRelInfo,
+ fdw_private,
+ i,
+ eflags);
+ }
+
resultRelInfo++;
i++;
}
@@ -1180,12 +1275,19 @@ 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)
+ char relkind;
+
+ relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind;
+ if (relkind == RELKIND_RELATION)
{
j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid");
if (!AttributeNumberIsValid(j->jf_junkAttNo))
elog(ERROR, "could not find junk ctid column");
}
+ else if (relkind == RELKIND_FOREIGN_TABLE)
+ {
+ /* FDW must fetch any junk attrs it wants */
+ }
else
{
j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow");
@@ -1244,6 +1346,19 @@ ExecEndModifyTable(ModifyTableState *node)
int i;
/*
+ * Allow any FDWs to shut down
+ */
+ for (i = 0; i < node->mt_nplans; i++)
+ {
+ ResultRelInfo *resultRelInfo = node->resultRelInfo + i;
+
+ if (resultRelInfo->ri_FdwRoutine != NULL &&
+ resultRelInfo->ri_FdwRoutine->EndForeignModify != NULL)
+ resultRelInfo->ri_FdwRoutine->EndForeignModify(node->ps.state,
+ resultRelInfo);
+ }
+
+ /*
* Free the exprcontext
*/
ExecFreeExprContext(&node->ps);