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.
This commit is contained in:
parent
7f49a67f95
commit
21734d2fb8
29 changed files with 3672 additions and 347 deletions
|
@ -118,7 +118,6 @@ SELECT tableoid::regclass, b FROM agg_csv;
|
|||
INSERT INTO agg_csv VALUES(1,2.0);
|
||||
UPDATE agg_csv SET a = 1;
|
||||
DELETE FROM agg_csv WHERE a = 100;
|
||||
SELECT * FROM agg_csv FOR UPDATE OF agg_csv;
|
||||
-- but this should be ignored
|
||||
SELECT * FROM agg_csv FOR UPDATE;
|
||||
|
||||
|
|
|
@ -185,15 +185,11 @@ SELECT tableoid::regclass, b FROM agg_csv;
|
|||
|
||||
-- updates aren't supported
|
||||
INSERT INTO agg_csv VALUES(1,2.0);
|
||||
ERROR: cannot change foreign table "agg_csv"
|
||||
ERROR: cannot insert into foreign table "agg_csv"
|
||||
UPDATE agg_csv SET a = 1;
|
||||
ERROR: cannot change foreign table "agg_csv"
|
||||
ERROR: cannot update foreign table "agg_csv"
|
||||
DELETE FROM agg_csv WHERE a = 100;
|
||||
ERROR: cannot change foreign table "agg_csv"
|
||||
SELECT * FROM agg_csv FOR UPDATE OF agg_csv;
|
||||
ERROR: row-level locks cannot be used with foreign table "agg_csv"
|
||||
LINE 1: SELECT * FROM agg_csv FOR UPDATE OF agg_csv;
|
||||
^
|
||||
ERROR: cannot delete from foreign table "agg_csv"
|
||||
-- but this should be ignored
|
||||
SELECT * FROM agg_csv FOR UPDATE;
|
||||
a | b
|
||||
|
|
|
@ -47,6 +47,8 @@ typedef struct ConnCacheEntry
|
|||
PGconn *conn; /* connection to foreign server, or NULL */
|
||||
int xact_depth; /* 0 = no xact open, 1 = main xact open, 2 =
|
||||
* one level of subxact open, etc */
|
||||
bool have_prep_stmt; /* have we prepared any stmts in this xact? */
|
||||
bool have_error; /* have any subxacts aborted in this xact? */
|
||||
} ConnCacheEntry;
|
||||
|
||||
/*
|
||||
|
@ -54,8 +56,9 @@ typedef struct ConnCacheEntry
|
|||
*/
|
||||
static HTAB *ConnectionHash = NULL;
|
||||
|
||||
/* for assigning cursor numbers */
|
||||
/* for assigning cursor numbers and prepared statement numbers */
|
||||
static unsigned int cursor_number = 0;
|
||||
static unsigned int prep_stmt_number = 0;
|
||||
|
||||
/* tracks whether any work is needed in callback functions */
|
||||
static bool xact_got_connection = false;
|
||||
|
@ -78,6 +81,10 @@ static void pgfdw_subxact_callback(SubXactEvent event,
|
|||
* if we don't already have a suitable one, and a transaction is opened at
|
||||
* the right subtransaction nesting depth if we didn't do that already.
|
||||
*
|
||||
* will_prep_stmt must be true if caller intends to create any prepared
|
||||
* statements. Since those don't go away automatically at transaction end
|
||||
* (not even on error), we need this flag to cue manual cleanup.
|
||||
*
|
||||
* XXX Note that caching connections theoretically requires a mechanism to
|
||||
* detect change of FDW objects to invalidate already established connections.
|
||||
* We could manage that by watching for invalidation events on the relevant
|
||||
|
@ -86,7 +93,8 @@ static void pgfdw_subxact_callback(SubXactEvent event,
|
|||
* mid-transaction anyway.
|
||||
*/
|
||||
PGconn *
|
||||
GetConnection(ForeignServer *server, UserMapping *user)
|
||||
GetConnection(ForeignServer *server, UserMapping *user,
|
||||
bool will_prep_stmt)
|
||||
{
|
||||
bool found;
|
||||
ConnCacheEntry *entry;
|
||||
|
@ -131,6 +139,8 @@ GetConnection(ForeignServer *server, UserMapping *user)
|
|||
/* initialize new hashtable entry (key is already filled in) */
|
||||
entry->conn = NULL;
|
||||
entry->xact_depth = 0;
|
||||
entry->have_prep_stmt = false;
|
||||
entry->have_error = false;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -147,6 +157,8 @@ GetConnection(ForeignServer *server, UserMapping *user)
|
|||
if (entry->conn == NULL)
|
||||
{
|
||||
entry->xact_depth = 0; /* just to be sure */
|
||||
entry->have_prep_stmt = false;
|
||||
entry->have_error = false;
|
||||
entry->conn = connect_pg_server(server, user);
|
||||
elog(DEBUG3, "new postgres_fdw connection %p for server \"%s\"",
|
||||
entry->conn, server->servername);
|
||||
|
@ -157,6 +169,9 @@ GetConnection(ForeignServer *server, UserMapping *user)
|
|||
*/
|
||||
begin_remote_xact(entry);
|
||||
|
||||
/* Remember if caller will prepare statements */
|
||||
entry->have_prep_stmt |= will_prep_stmt;
|
||||
|
||||
return entry->conn;
|
||||
}
|
||||
|
||||
|
@ -393,6 +408,20 @@ GetCursorNumber(PGconn *conn)
|
|||
return ++cursor_number;
|
||||
}
|
||||
|
||||
/*
|
||||
* Assign a "unique" number for a prepared statement.
|
||||
*
|
||||
* This works much like GetCursorNumber, except that we never reset the counter
|
||||
* within a session. That's because we can't be 100% sure we've gotten rid
|
||||
* of all prepared statements on all connections, and it's not really worth
|
||||
* increasing the risk of prepared-statement name collisions by resetting.
|
||||
*/
|
||||
unsigned int
|
||||
GetPrepStmtNumber(PGconn *conn)
|
||||
{
|
||||
return ++prep_stmt_number;
|
||||
}
|
||||
|
||||
/*
|
||||
* Report an error we got from the remote server.
|
||||
*
|
||||
|
@ -400,6 +429,10 @@ GetCursorNumber(PGconn *conn)
|
|||
* res: PGresult containing the error
|
||||
* clear: if true, PQclear the result (otherwise caller will handle it)
|
||||
* sql: NULL, or text of remote command we tried to execute
|
||||
*
|
||||
* Note: callers that choose not to throw ERROR for a remote error are
|
||||
* responsible for making sure that the associated ConnCacheEntry gets
|
||||
* marked with have_error = true.
|
||||
*/
|
||||
void
|
||||
pgfdw_report_error(int elevel, PGresult *res, bool clear, const char *sql)
|
||||
|
@ -480,6 +513,22 @@ pgfdw_xact_callback(XactEvent event, void *arg)
|
|||
if (PQresultStatus(res) != PGRES_COMMAND_OK)
|
||||
pgfdw_report_error(ERROR, res, true, "COMMIT TRANSACTION");
|
||||
PQclear(res);
|
||||
|
||||
/*
|
||||
* If there were any errors in subtransactions, and we made
|
||||
* prepared statements, do a DEALLOCATE ALL to make sure we
|
||||
* get rid of all prepared statements. This is annoying and
|
||||
* not terribly bulletproof, but it's probably not worth
|
||||
* trying harder. We intentionally ignore any errors in the
|
||||
* DEALLOCATE.
|
||||
*/
|
||||
if (entry->have_prep_stmt && entry->have_error)
|
||||
{
|
||||
res = PQexec(entry->conn, "DEALLOCATE ALL");
|
||||
PQclear(res);
|
||||
}
|
||||
entry->have_prep_stmt = false;
|
||||
entry->have_error = false;
|
||||
break;
|
||||
case XACT_EVENT_PRE_PREPARE:
|
||||
|
||||
|
@ -502,6 +551,8 @@ pgfdw_xact_callback(XactEvent event, void *arg)
|
|||
elog(ERROR, "missed cleaning up connection during pre-commit");
|
||||
break;
|
||||
case XACT_EVENT_ABORT:
|
||||
/* Assume we might have lost track of prepared statements */
|
||||
entry->have_error = true;
|
||||
/* If we're aborting, abort all remote transactions too */
|
||||
res = PQexec(entry->conn, "ABORT TRANSACTION");
|
||||
/* Note: can't throw ERROR, it would be infinite loop */
|
||||
|
@ -509,7 +560,17 @@ pgfdw_xact_callback(XactEvent event, void *arg)
|
|||
pgfdw_report_error(WARNING, res, true,
|
||||
"ABORT TRANSACTION");
|
||||
else
|
||||
{
|
||||
PQclear(res);
|
||||
/* As above, make sure we've cleared any prepared stmts */
|
||||
if (entry->have_prep_stmt && entry->have_error)
|
||||
{
|
||||
res = PQexec(entry->conn, "DEALLOCATE ALL");
|
||||
PQclear(res);
|
||||
}
|
||||
entry->have_prep_stmt = false;
|
||||
entry->have_error = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -593,6 +654,8 @@ pgfdw_subxact_callback(SubXactEvent event, SubTransactionId mySubid,
|
|||
}
|
||||
else
|
||||
{
|
||||
/* Assume we might have lost track of prepared statements */
|
||||
entry->have_error = true;
|
||||
/* Rollback all remote subtransactions during abort */
|
||||
snprintf(sql, sizeof(sql),
|
||||
"ROLLBACK TO SAVEPOINT s%d; RELEASE SAVEPOINT s%d",
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
|
||||
#include "postgres_fdw.h"
|
||||
|
||||
#include "access/heapam.h"
|
||||
#include "access/htup_details.h"
|
||||
#include "access/sysattr.h"
|
||||
#include "access/transam.h"
|
||||
|
@ -66,6 +67,14 @@ static bool is_builtin(Oid procid);
|
|||
/*
|
||||
* Functions to construct string representation of a node tree.
|
||||
*/
|
||||
static void deparseTargetList(StringInfo buf,
|
||||
PlannerInfo *root,
|
||||
Index rtindex,
|
||||
Relation rel,
|
||||
Bitmapset *attrs_used);
|
||||
static void deparseReturningList(StringInfo buf, PlannerInfo *root,
|
||||
Index rtindex, Relation rel,
|
||||
List *returningList);
|
||||
static void deparseColumnRef(StringInfo buf, int varno, int varattno,
|
||||
PlannerInfo *root);
|
||||
static void deparseRelation(StringInfo buf, Oid relid);
|
||||
|
@ -349,80 +358,104 @@ is_builtin(Oid oid)
|
|||
|
||||
|
||||
/*
|
||||
* Construct a simple SELECT statement that retrieves interesting columns
|
||||
* Construct a simple SELECT statement that retrieves desired columns
|
||||
* of the specified foreign table, and append it to "buf". The output
|
||||
* contains just "SELECT ... FROM tablename".
|
||||
*
|
||||
* "Interesting" columns are those appearing in the rel's targetlist or
|
||||
* in local_conds (conditions which can't be executed remotely).
|
||||
*/
|
||||
void
|
||||
deparseSimpleSql(StringInfo buf,
|
||||
deparseSelectSql(StringInfo buf,
|
||||
PlannerInfo *root,
|
||||
RelOptInfo *baserel,
|
||||
List *local_conds)
|
||||
Bitmapset *attrs_used)
|
||||
{
|
||||
RangeTblEntry *rte = root->simple_rte_array[baserel->relid];
|
||||
Bitmapset *attrs_used = NULL;
|
||||
RangeTblEntry *rte = planner_rt_fetch(baserel->relid, root);
|
||||
Relation rel;
|
||||
|
||||
/*
|
||||
* Core code already has some lock on each rel being planned, so we can
|
||||
* use NoLock here.
|
||||
*/
|
||||
rel = heap_open(rte->relid, NoLock);
|
||||
|
||||
/*
|
||||
* Construct SELECT list
|
||||
*/
|
||||
appendStringInfoString(buf, "SELECT ");
|
||||
deparseTargetList(buf, root, baserel->relid, rel, attrs_used);
|
||||
|
||||
/*
|
||||
* Construct FROM clause
|
||||
*/
|
||||
appendStringInfoString(buf, " FROM ");
|
||||
deparseRelation(buf, RelationGetRelid(rel));
|
||||
|
||||
heap_close(rel, NoLock);
|
||||
}
|
||||
|
||||
/*
|
||||
* Emit a target list that retrieves the columns specified in attrs_used.
|
||||
* This is used for both SELECT and RETURNING targetlists.
|
||||
*
|
||||
* We list attributes in order of the foreign table's columns, but replace
|
||||
* any attributes that need not be fetched with NULL constants. (We can't
|
||||
* 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
|
||||
deparseTargetList(StringInfo buf,
|
||||
PlannerInfo *root,
|
||||
Index rtindex,
|
||||
Relation rel,
|
||||
Bitmapset *attrs_used)
|
||||
{
|
||||
TupleDesc tupdesc = RelationGetDescr(rel);
|
||||
bool have_wholerow;
|
||||
bool first;
|
||||
AttrNumber attr;
|
||||
ListCell *lc;
|
||||
|
||||
/* Collect all the attributes needed for joins or final output. */
|
||||
pull_varattnos((Node *) baserel->reltargetlist, baserel->relid,
|
||||
&attrs_used);
|
||||
|
||||
/* Add all the attributes used by local_conds. */
|
||||
foreach(lc, local_conds)
|
||||
{
|
||||
RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
|
||||
|
||||
pull_varattnos((Node *) rinfo->clause, baserel->relid,
|
||||
&attrs_used);
|
||||
}
|
||||
int i;
|
||||
|
||||
/* If there's a whole-row reference, we'll need all the columns. */
|
||||
have_wholerow = bms_is_member(0 - FirstLowInvalidHeapAttributeNumber,
|
||||
attrs_used);
|
||||
|
||||
/*
|
||||
* Construct SELECT list
|
||||
*
|
||||
* We list attributes in order of the foreign table's columns, but replace
|
||||
* any attributes that need not be fetched with NULL constants. (We can't
|
||||
* just omit such attributes, or we'll lose track of which columns are
|
||||
* which at runtime.) Note however that any dropped columns are ignored.
|
||||
*/
|
||||
appendStringInfo(buf, "SELECT ");
|
||||
first = true;
|
||||
for (attr = 1; attr <= baserel->max_attr; attr++)
|
||||
for (i = 1; i <= tupdesc->natts; i++)
|
||||
{
|
||||
Form_pg_attribute attr = tupdesc->attrs[i - 1];
|
||||
|
||||
/* Ignore dropped attributes. */
|
||||
if (get_rte_attribute_is_dropped(rte, attr))
|
||||
if (attr->attisdropped)
|
||||
continue;
|
||||
|
||||
if (!first)
|
||||
appendStringInfo(buf, ", ");
|
||||
appendStringInfoString(buf, ", ");
|
||||
first = false;
|
||||
|
||||
if (have_wholerow ||
|
||||
bms_is_member(attr - FirstLowInvalidHeapAttributeNumber,
|
||||
bms_is_member(i - FirstLowInvalidHeapAttributeNumber,
|
||||
attrs_used))
|
||||
deparseColumnRef(buf, baserel->relid, attr, root);
|
||||
deparseColumnRef(buf, rtindex, i, root);
|
||||
else
|
||||
appendStringInfo(buf, "NULL");
|
||||
appendStringInfoString(buf, "NULL");
|
||||
}
|
||||
|
||||
/*
|
||||
* Add ctid if needed. We currently don't support retrieving any other
|
||||
* system columns.
|
||||
*/
|
||||
if (bms_is_member(SelfItemPointerAttributeNumber - FirstLowInvalidHeapAttributeNumber,
|
||||
attrs_used))
|
||||
{
|
||||
if (!first)
|
||||
appendStringInfoString(buf, ", ");
|
||||
first = false;
|
||||
|
||||
appendStringInfoString(buf, "ctid");
|
||||
}
|
||||
|
||||
/* Don't generate bad syntax if no undropped columns */
|
||||
if (first)
|
||||
appendStringInfo(buf, "NULL");
|
||||
|
||||
/*
|
||||
* Construct FROM clause
|
||||
*/
|
||||
appendStringInfo(buf, " FROM ");
|
||||
deparseRelation(buf, rte->relid);
|
||||
appendStringInfoString(buf, "NULL");
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -432,9 +465,9 @@ deparseSimpleSql(StringInfo buf,
|
|||
*/
|
||||
void
|
||||
appendWhereClause(StringInfo buf,
|
||||
bool is_first,
|
||||
PlannerInfo *root,
|
||||
List *exprs,
|
||||
PlannerInfo *root)
|
||||
bool is_first)
|
||||
{
|
||||
ListCell *lc;
|
||||
|
||||
|
@ -444,9 +477,9 @@ appendWhereClause(StringInfo buf,
|
|||
|
||||
/* Connect expressions with "AND" and parenthesize each condition. */
|
||||
if (is_first)
|
||||
appendStringInfo(buf, " WHERE ");
|
||||
appendStringInfoString(buf, " WHERE ");
|
||||
else
|
||||
appendStringInfo(buf, " AND ");
|
||||
appendStringInfoString(buf, " AND ");
|
||||
|
||||
appendStringInfoChar(buf, '(');
|
||||
deparseExpr(buf, ri->clause, root);
|
||||
|
@ -456,6 +489,147 @@ appendWhereClause(StringInfo buf,
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* deparse remote INSERT statement
|
||||
*/
|
||||
void
|
||||
deparseInsertSql(StringInfo buf, PlannerInfo *root, Index rtindex,
|
||||
List *targetAttrs, List *returningList)
|
||||
{
|
||||
RangeTblEntry *rte = planner_rt_fetch(rtindex, root);
|
||||
Relation rel = heap_open(rte->relid, NoLock);
|
||||
TupleDesc tupdesc = RelationGetDescr(rel);
|
||||
AttrNumber pindex;
|
||||
bool first;
|
||||
ListCell *lc;
|
||||
|
||||
appendStringInfoString(buf, "INSERT INTO ");
|
||||
deparseRelation(buf, rte->relid);
|
||||
appendStringInfoString(buf, "(");
|
||||
|
||||
first = true;
|
||||
foreach(lc, targetAttrs)
|
||||
{
|
||||
int attnum = lfirst_int(lc);
|
||||
Form_pg_attribute attr = tupdesc->attrs[attnum - 1];
|
||||
|
||||
Assert(!attr->attisdropped);
|
||||
|
||||
if (!first)
|
||||
appendStringInfoString(buf, ", ");
|
||||
first = false;
|
||||
|
||||
deparseColumnRef(buf, rtindex, attnum, root);
|
||||
}
|
||||
|
||||
appendStringInfoString(buf, ") VALUES (");
|
||||
|
||||
pindex = 1;
|
||||
first = true;
|
||||
foreach(lc, targetAttrs)
|
||||
{
|
||||
if (!first)
|
||||
appendStringInfoString(buf, ", ");
|
||||
first = false;
|
||||
|
||||
appendStringInfo(buf, "$%d", pindex);
|
||||
pindex++;
|
||||
}
|
||||
|
||||
appendStringInfoString(buf, ")");
|
||||
|
||||
if (returningList)
|
||||
deparseReturningList(buf, root, rtindex, rel, returningList);
|
||||
|
||||
heap_close(rel, NoLock);
|
||||
}
|
||||
|
||||
/*
|
||||
* deparse remote UPDATE statement
|
||||
*/
|
||||
void
|
||||
deparseUpdateSql(StringInfo buf, PlannerInfo *root, Index rtindex,
|
||||
List *targetAttrs, List *returningList)
|
||||
{
|
||||
RangeTblEntry *rte = planner_rt_fetch(rtindex, root);
|
||||
Relation rel = heap_open(rte->relid, NoLock);
|
||||
TupleDesc tupdesc = RelationGetDescr(rel);
|
||||
AttrNumber pindex;
|
||||
bool first;
|
||||
ListCell *lc;
|
||||
|
||||
appendStringInfoString(buf, "UPDATE ");
|
||||
deparseRelation(buf, rte->relid);
|
||||
appendStringInfoString(buf, " SET ");
|
||||
|
||||
pindex = 2; /* ctid is always the first param */
|
||||
first = true;
|
||||
foreach(lc, targetAttrs)
|
||||
{
|
||||
int attnum = lfirst_int(lc);
|
||||
Form_pg_attribute attr = tupdesc->attrs[attnum - 1];
|
||||
|
||||
Assert(!attr->attisdropped);
|
||||
|
||||
if (!first)
|
||||
appendStringInfoString(buf, ", ");
|
||||
first = false;
|
||||
|
||||
deparseColumnRef(buf, rtindex, attnum, root);
|
||||
appendStringInfo(buf, " = $%d", pindex);
|
||||
pindex++;
|
||||
}
|
||||
appendStringInfoString(buf, " WHERE ctid = $1");
|
||||
|
||||
if (returningList)
|
||||
deparseReturningList(buf, root, rtindex, rel, returningList);
|
||||
|
||||
heap_close(rel, NoLock);
|
||||
}
|
||||
|
||||
/*
|
||||
* deparse remote DELETE statement
|
||||
*/
|
||||
void
|
||||
deparseDeleteSql(StringInfo buf, PlannerInfo *root, Index rtindex,
|
||||
List *returningList)
|
||||
{
|
||||
RangeTblEntry *rte = planner_rt_fetch(rtindex, root);
|
||||
|
||||
appendStringInfoString(buf, "DELETE FROM ");
|
||||
deparseRelation(buf, rte->relid);
|
||||
appendStringInfoString(buf, " WHERE ctid = $1");
|
||||
|
||||
if (returningList)
|
||||
{
|
||||
Relation rel = heap_open(rte->relid, NoLock);
|
||||
|
||||
deparseReturningList(buf, root, rtindex, rel, returningList);
|
||||
heap_close(rel, NoLock);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* deparse RETURNING clause of INSERT/UPDATE/DELETE
|
||||
*/
|
||||
static void
|
||||
deparseReturningList(StringInfo buf, PlannerInfo *root,
|
||||
Index rtindex, Relation rel,
|
||||
List *returningList)
|
||||
{
|
||||
Bitmapset *attrs_used;
|
||||
|
||||
/*
|
||||
* We need the attrs mentioned in the query's RETURNING list.
|
||||
*/
|
||||
attrs_used = NULL;
|
||||
pull_varattnos((Node *) returningList, rtindex,
|
||||
&attrs_used);
|
||||
|
||||
appendStringInfoString(buf, " RETURNING ");
|
||||
deparseTargetList(buf, root, rtindex, rel, attrs_used);
|
||||
}
|
||||
|
||||
/*
|
||||
* Construct SELECT statement to acquire size in blocks of given relation.
|
||||
*
|
||||
|
@ -495,13 +669,17 @@ deparseAnalyzeSql(StringInfo buf, Relation rel)
|
|||
ListCell *lc;
|
||||
bool first = true;
|
||||
|
||||
appendStringInfo(buf, "SELECT ");
|
||||
appendStringInfoString(buf, "SELECT ");
|
||||
for (i = 0; i < tupdesc->natts; i++)
|
||||
{
|
||||
/* Ignore dropped columns. */
|
||||
if (tupdesc->attrs[i]->attisdropped)
|
||||
continue;
|
||||
|
||||
if (!first)
|
||||
appendStringInfoString(buf, ", ");
|
||||
first = false;
|
||||
|
||||
/* Use attribute name or column_name option. */
|
||||
colname = NameStr(tupdesc->attrs[i]->attname);
|
||||
options = GetForeignColumnOptions(relid, i + 1);
|
||||
|
@ -517,20 +695,17 @@ deparseAnalyzeSql(StringInfo buf, Relation rel)
|
|||
}
|
||||
}
|
||||
|
||||
if (!first)
|
||||
appendStringInfo(buf, ", ");
|
||||
appendStringInfoString(buf, quote_identifier(colname));
|
||||
first = false;
|
||||
}
|
||||
|
||||
/* Don't generate bad syntax for zero-column relation. */
|
||||
if (first)
|
||||
appendStringInfo(buf, "NULL");
|
||||
appendStringInfoString(buf, "NULL");
|
||||
|
||||
/*
|
||||
* Construct FROM clause
|
||||
*/
|
||||
appendStringInfo(buf, " FROM ");
|
||||
appendStringInfoString(buf, " FROM ");
|
||||
deparseRelation(buf, relid);
|
||||
}
|
||||
|
||||
|
@ -547,10 +722,10 @@ deparseColumnRef(StringInfo buf, int varno, int varattno, PlannerInfo *root)
|
|||
ListCell *lc;
|
||||
|
||||
/* varno must not be any of OUTER_VAR, INNER_VAR and INDEX_VAR. */
|
||||
Assert(varno >= 1 && varno <= root->simple_rel_array_size);
|
||||
Assert(!IS_SPECIAL_VARNO(varno));
|
||||
|
||||
/* Get RangeTblEntry from array in PlannerInfo. */
|
||||
rte = root->simple_rte_array[varno];
|
||||
rte = planner_rt_fetch(varno, root);
|
||||
|
||||
/*
|
||||
* If it's a column of a foreign table, and it has the column_name FDW
|
||||
|
@ -608,8 +783,8 @@ deparseRelation(StringInfo buf, Oid relid)
|
|||
}
|
||||
|
||||
/*
|
||||
* Note: we could skip printing the schema name if it's pg_catalog,
|
||||
* but that doesn't seem worth the trouble.
|
||||
* Note: we could skip printing the schema name if it's pg_catalog, but
|
||||
* that doesn't seem worth the trouble.
|
||||
*/
|
||||
if (nspname == NULL)
|
||||
nspname = get_namespace_name(get_rel_namespace(relid));
|
||||
|
@ -1059,7 +1234,7 @@ deparseDistinctExpr(StringInfo buf, DistinctExpr *node, PlannerInfo *root)
|
|||
|
||||
appendStringInfoChar(buf, '(');
|
||||
deparseExpr(buf, linitial(node->args), root);
|
||||
appendStringInfo(buf, " IS DISTINCT FROM ");
|
||||
appendStringInfoString(buf, " IS DISTINCT FROM ");
|
||||
deparseExpr(buf, lsecond(node->args), root);
|
||||
appendStringInfoChar(buf, ')');
|
||||
}
|
||||
|
@ -1146,7 +1321,7 @@ deparseBoolExpr(StringInfo buf, BoolExpr *node, PlannerInfo *root)
|
|||
op = "OR";
|
||||
break;
|
||||
case NOT_EXPR:
|
||||
appendStringInfo(buf, "(NOT ");
|
||||
appendStringInfoString(buf, "(NOT ");
|
||||
deparseExpr(buf, linitial(node->args), root);
|
||||
appendStringInfoChar(buf, ')');
|
||||
return;
|
||||
|
@ -1173,9 +1348,9 @@ deparseNullTest(StringInfo buf, NullTest *node, PlannerInfo *root)
|
|||
appendStringInfoChar(buf, '(');
|
||||
deparseExpr(buf, node->arg, root);
|
||||
if (node->nulltesttype == IS_NULL)
|
||||
appendStringInfo(buf, " IS NULL)");
|
||||
appendStringInfoString(buf, " IS NULL)");
|
||||
else
|
||||
appendStringInfo(buf, " IS NOT NULL)");
|
||||
appendStringInfoString(buf, " IS NOT NULL)");
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -1187,11 +1362,11 @@ deparseArrayExpr(StringInfo buf, ArrayExpr *node, PlannerInfo *root)
|
|||
bool first = true;
|
||||
ListCell *lc;
|
||||
|
||||
appendStringInfo(buf, "ARRAY[");
|
||||
appendStringInfoString(buf, "ARRAY[");
|
||||
foreach(lc, node->elements)
|
||||
{
|
||||
if (!first)
|
||||
appendStringInfo(buf, ", ");
|
||||
appendStringInfoString(buf, ", ");
|
||||
deparseExpr(buf, lfirst(lc), root);
|
||||
first = false;
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -21,9 +21,11 @@
|
|||
#include "libpq-fe.h"
|
||||
|
||||
/* in connection.c */
|
||||
extern PGconn *GetConnection(ForeignServer *server, UserMapping *user);
|
||||
extern PGconn *GetConnection(ForeignServer *server, UserMapping *user,
|
||||
bool will_prep_stmt);
|
||||
extern void ReleaseConnection(PGconn *conn);
|
||||
extern unsigned int GetCursorNumber(PGconn *conn);
|
||||
extern unsigned int GetPrepStmtNumber(PGconn *conn);
|
||||
extern void pgfdw_report_error(int elevel, PGresult *res, bool clear,
|
||||
const char *sql);
|
||||
|
||||
|
@ -39,14 +41,20 @@ extern void classifyConditions(PlannerInfo *root,
|
|||
List **param_conds,
|
||||
List **local_conds,
|
||||
List **param_numbers);
|
||||
extern void deparseSimpleSql(StringInfo buf,
|
||||
extern void deparseSelectSql(StringInfo buf,
|
||||
PlannerInfo *root,
|
||||
RelOptInfo *baserel,
|
||||
List *local_conds);
|
||||
Bitmapset *attrs_used);
|
||||
extern void appendWhereClause(StringInfo buf,
|
||||
bool has_where,
|
||||
PlannerInfo *root,
|
||||
List *exprs,
|
||||
PlannerInfo *root);
|
||||
bool is_first);
|
||||
extern void deparseInsertSql(StringInfo buf, PlannerInfo *root, Index rtindex,
|
||||
List *targetAttrs, List *returningList);
|
||||
extern void deparseUpdateSql(StringInfo buf, PlannerInfo *root, Index rtindex,
|
||||
List *targetAttrs, List *returningList);
|
||||
extern void deparseDeleteSql(StringInfo buf, PlannerInfo *root, Index rtindex,
|
||||
List *returningList);
|
||||
extern void deparseAnalyzeSizeSql(StringInfo buf, Relation rel);
|
||||
extern void deparseAnalyzeSql(StringInfo buf, Relation rel);
|
||||
|
||||
|
|
|
@ -273,3 +273,77 @@ ROLLBACK TO s;
|
|||
FETCH c;
|
||||
SELECT * FROM ft1 ORDER BY c1 LIMIT 1;
|
||||
COMMIT;
|
||||
|
||||
-- ===================================================================
|
||||
-- test writable foreign table stuff
|
||||
-- ===================================================================
|
||||
EXPLAIN (verbose, costs off)
|
||||
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;
|
||||
INSERT INTO ft2 (c1,c2,c3)
|
||||
VALUES (1101,201,'aaa'), (1102,202,'bbb'), (1103,203,'ccc') RETURNING *;
|
||||
INSERT INTO ft2 (c1,c2,c3) VALUES (1104,204,'ddd'), (1105,205,'eee');
|
||||
UPDATE ft2 SET c2 = c2 + 300, c3 = c3 || '_update3' WHERE c1 % 10 = 3;
|
||||
UPDATE ft2 SET c2 = c2 + 400, c3 = c3 || '_update7' WHERE c1 % 10 = 7 RETURNING *;
|
||||
EXPLAIN (verbose, costs off)
|
||||
UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9'
|
||||
FROM ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 9;
|
||||
UPDATE ft2 SET c2 = ft2.c2 + 500, c3 = ft2.c3 || '_update9'
|
||||
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 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2;
|
||||
DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2;
|
||||
SELECT c1,c2,c3,c4 FROM ft2 ORDER BY c1;
|
||||
|
||||
-- Test that defaults and triggers on remote table work as expected
|
||||
ALTER TABLE "S 1"."T 1" ALTER c6 SET DEFAULT '(^-^;)';
|
||||
CREATE OR REPLACE FUNCTION "S 1".F_BRTRIG() RETURNS trigger AS $$
|
||||
BEGIN
|
||||
NEW.c3 = NEW.c3 || '_trig_update';
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
CREATE TRIGGER t1_br_insert BEFORE INSERT OR UPDATE
|
||||
ON "S 1"."T 1" FOR EACH ROW EXECUTE PROCEDURE "S 1".F_BRTRIG();
|
||||
|
||||
INSERT INTO ft2 (c1,c2,c3) VALUES (1208, 218, 'fff') RETURNING *;
|
||||
INSERT INTO ft2 (c1,c2,c3,c6) VALUES (1218, 218, 'ggg', '(--;') RETURNING *;
|
||||
UPDATE ft2 SET c2 = c2 + 600 WHERE c1 % 10 = 8 RETURNING *;
|
||||
|
||||
-- Test errors thrown on remote side during update
|
||||
ALTER TABLE "S 1"."T 1" ADD CONSTRAINT c2positive CHECK (c2 >= 0);
|
||||
|
||||
INSERT INTO ft1(c1, c2) VALUES(11, 12); -- duplicate key
|
||||
INSERT INTO ft1(c1, c2) VALUES(1111, -2); -- c2positive
|
||||
UPDATE ft1 SET c2 = -c2 WHERE c1 = 1; -- c2positive
|
||||
|
||||
-- Test savepoint/rollback behavior
|
||||
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
|
||||
select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1;
|
||||
begin;
|
||||
update ft2 set c2 = 42 where c2 = 0;
|
||||
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
|
||||
savepoint s1;
|
||||
update ft2 set c2 = 44 where c2 = 4;
|
||||
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
|
||||
release savepoint s1;
|
||||
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
|
||||
savepoint s2;
|
||||
update ft2 set c2 = 46 where c2 = 6;
|
||||
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
|
||||
rollback to savepoint s2;
|
||||
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
|
||||
release savepoint s2;
|
||||
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
|
||||
savepoint s3;
|
||||
update ft2 set c2 = -2 where c2 = 42; -- fail on remote side
|
||||
rollback to savepoint s3;
|
||||
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
|
||||
release savepoint s3;
|
||||
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
|
||||
-- none of the above is committed yet remotely
|
||||
select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1;
|
||||
commit;
|
||||
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
|
||||
select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1;
|
||||
|
|
|
@ -3040,36 +3040,41 @@ ANALYZE measurement;
|
|||
Foreign data is accessed with help from a
|
||||
<firstterm>foreign data wrapper</firstterm>. A foreign data wrapper is a
|
||||
library that can communicate with an external data source, hiding the
|
||||
details of connecting to the data source and fetching data from it. There
|
||||
is a foreign data wrapper available as a <filename>contrib</> module,
|
||||
which can read plain data files residing on the server. Other kind of
|
||||
foreign data wrappers might be found as third party products. If none of
|
||||
the existing foreign data wrappers suit your needs, you can write your
|
||||
own; see <xref linkend="fdwhandler">.
|
||||
details of connecting to the data source and obtaining data from it.
|
||||
There are some foreign data wrappers available as <filename>contrib</>
|
||||
modules; see <xref linkend="contrib">. Other kinds of foreign data
|
||||
wrappers might be found as third party products. If none of the existing
|
||||
foreign data wrappers suit your needs, you can write your own; see <xref
|
||||
linkend="fdwhandler">.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
To access foreign data, you need to create a <firstterm>foreign server</>
|
||||
object, which defines how to connect to a particular external data source,
|
||||
according to the set of options used by a particular foreign data
|
||||
object, which defines how to connect to a particular external data source
|
||||
according to the set of options used by its supporting foreign data
|
||||
wrapper. Then you need to create one or more <firstterm>foreign
|
||||
tables</firstterm>, which define the structure of the remote data. A
|
||||
foreign table can be used in queries just like a normal table, but a
|
||||
foreign table has no storage in the PostgreSQL server. Whenever it is
|
||||
used, <productname>PostgreSQL</productname> asks the foreign data wrapper
|
||||
to fetch the data from the external source.
|
||||
to fetch data from the external source, or transmit data to the external
|
||||
source in the case of update commands.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Accessing remote data may require authentication at the external
|
||||
Accessing remote data may require authenticating to the external
|
||||
data source. This information can be provided by a
|
||||
<firstterm>user mapping</>, which can provide additional options based
|
||||
<firstterm>user mapping</>, which can provide additional data
|
||||
such as user names and passwords 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.
|
||||
For additional information, see
|
||||
<xref linkend="sql-createforeigndatawrapper">,
|
||||
<xref linkend="sql-createserver">,
|
||||
<xref linkend="sql-createusermapping">, and
|
||||
<xref linkend="sql-createforeigntable">.
|
||||
</para>
|
||||
</sect1>
|
||||
|
||||
|
|
|
@ -13,14 +13,15 @@
|
|||
wrapper, which consists of a set of functions that the core server
|
||||
calls. The foreign data wrapper is responsible for fetching
|
||||
data from the remote data source and returning it to the
|
||||
<productname>PostgreSQL</productname> executor. This chapter outlines how
|
||||
to write a new foreign data wrapper.
|
||||
<productname>PostgreSQL</productname> executor. If updating foreign
|
||||
tables is to be supported, the wrapper must handle that, too.
|
||||
This chapter outlines how to write a new foreign data wrapper.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
The foreign data wrappers included in the standard distribution are good
|
||||
references when trying to write your own. Look into the
|
||||
<filename>contrib/file_fdw</> subdirectory of the source tree.
|
||||
<filename>contrib</> subdirectory of the source tree.
|
||||
The <xref linkend="sql-createforeigndatawrapper"> reference page also has
|
||||
some useful details.
|
||||
</para>
|
||||
|
@ -84,9 +85,19 @@
|
|||
|
||||
<para>
|
||||
The FDW handler function returns a palloc'd <structname>FdwRoutine</>
|
||||
struct containing pointers to the following callback functions:
|
||||
struct containing pointers to the callback functions described below.
|
||||
The scan-related functions are required, the rest are optional.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
The <structname>FdwRoutine</> struct type is declared in
|
||||
<filename>src/include/foreign/fdwapi.h</>, which see for additional
|
||||
details.
|
||||
</para>
|
||||
|
||||
<sect2 id="fdw-callbacks-scan">
|
||||
<title>FDW Routines For Scanning Foreign Tables</title>
|
||||
|
||||
<para>
|
||||
<programlisting>
|
||||
void
|
||||
|
@ -96,7 +107,7 @@ 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.
|
||||
at the beginning of planning for a query that scans a foreign table.
|
||||
<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
|
||||
|
@ -181,23 +192,6 @@ GetForeignPlan (PlannerInfo *root,
|
|||
<para>
|
||||
<programlisting>
|
||||
void
|
||||
ExplainForeignScan (ForeignScanState *node,
|
||||
ExplainState *es);
|
||||
</programlisting>
|
||||
|
||||
Print additional <command>EXPLAIN</> output for a foreign table scan.
|
||||
This can just return if there is no need to print anything.
|
||||
Otherwise, it should call <function>ExplainPropertyText</> and
|
||||
related functions to add fields to the <command>EXPLAIN</> output.
|
||||
The flag fields in <literal>es</> can be used to determine what to
|
||||
print, and the state of the <structname>ForeignScanState</> node
|
||||
can be inspected to provide run-time statistics in the <command>EXPLAIN
|
||||
ANALYZE</> case.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
<programlisting>
|
||||
void
|
||||
BeginForeignScan (ForeignScanState *node,
|
||||
int eflags);
|
||||
</programlisting>
|
||||
|
@ -212,6 +206,8 @@ BeginForeignScan (ForeignScanState *node,
|
|||
<structname>ForeignScanState</> node (in particular, from the underlying
|
||||
<structname>ForeignScan</> plan node, which contains any FDW-private
|
||||
information provided by <function>GetForeignPlan</>).
|
||||
<literal>eflags</> contains flag bits describing the executor's
|
||||
operating mode for this plan node.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
|
@ -246,9 +242,9 @@ IterateForeignScan (ForeignScanState *node);
|
|||
|
||||
<para>
|
||||
Note that <productname>PostgreSQL</productname>'s executor doesn't care
|
||||
whether the rows returned violate the <literal>NOT NULL</literal>
|
||||
constraints which were defined on the foreign table columns - but the
|
||||
planner does care, and may optimize queries incorrectly if
|
||||
whether the rows returned violate any <literal>NOT NULL</literal>
|
||||
constraints that were defined on the foreign table columns — but
|
||||
the planner does care, and may optimize queries incorrectly if
|
||||
<literal>NULL</> values are present in a column declared not to contain
|
||||
them. If a <literal>NULL</> value is encountered when the user has
|
||||
declared that none should be present, it may be appropriate to raise an
|
||||
|
@ -277,6 +273,356 @@ EndForeignScan (ForeignScanState *node);
|
|||
to remote servers should be cleaned up.
|
||||
</para>
|
||||
|
||||
</sect2>
|
||||
|
||||
<sect2 id="fdw-callbacks-update">
|
||||
<title>FDW Routines For Updating Foreign Tables</title>
|
||||
|
||||
<para>
|
||||
If an FDW supports writable foreign tables, it should provide
|
||||
some or all of the following callback functions depending on
|
||||
the needs and capabilities of the FDW:
|
||||
</para>
|
||||
|
||||
<para>
|
||||
<programlisting>
|
||||
void
|
||||
AddForeignUpdateTargets (Query *parsetree,
|
||||
RangeTblEntry *target_rte,
|
||||
Relation target_relation);
|
||||
</programlisting>
|
||||
|
||||
<command>UPDATE</> and <command>DELETE</> operations are performed
|
||||
against rows previously fetched by the table-scanning functions. The
|
||||
FDW may need extra information, such as a row ID or the values of
|
||||
primary-key columns, to ensure that it can identify the exact row to
|
||||
update or delete. To support that, this function can add extra hidden,
|
||||
or <quote>junk</>, target columns to the list of columns that are to be
|
||||
retrieved from the foreign table during an <command>UPDATE</> or
|
||||
<command>DELETE</>.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
To do that, add <structname>TargetEntry</> items to
|
||||
<literal>parsetree->targetList</>, containing expressions for the
|
||||
extra values to be fetched. Each such entry must be marked
|
||||
<structfield>resjunk</> = <literal>true</>, and must have a distinct
|
||||
<structfield>resname</> that will identify it at execution time.
|
||||
Avoid using names matching <literal>ctid<replaceable>N</></literal> or
|
||||
<literal>wholerow<replaceable>N</></literal>, as the core system can
|
||||
generate junk columns of these names.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
This function is called in the rewriter, not the planner, so the
|
||||
information available is a bit different from that available to the
|
||||
planning routines.
|
||||
<literal>parsetree</> is the parse tree for the <command>UPDATE</> or
|
||||
<command>DELETE</> command, while <literal>target_rte</> and
|
||||
<literal>target_relation</> describe the target foreign table.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
If the <function>AddForeignUpdateTargets</> pointer is set to
|
||||
<literal>NULL</>, no extra target expressions are added.
|
||||
(This will make it impossible to implement <command>DELETE</>
|
||||
operations, though <command>UPDATE</> may still be feasible if the FDW
|
||||
relies on an unchanging primary key to identify rows.)
|
||||
</para>
|
||||
|
||||
<para>
|
||||
<programlisting>
|
||||
List *
|
||||
PlanForeignModify (PlannerInfo *root,
|
||||
ModifyTable *plan,
|
||||
Index resultRelation,
|
||||
int subplan_index);
|
||||
</programlisting>
|
||||
|
||||
Perform any additional planning actions needed for an insert, update, or
|
||||
delete on a foreign table. This function generates the FDW-private
|
||||
information that will be attached to the <structname>ModifyTable</> plan
|
||||
node that performs the update action. This private information must
|
||||
have the form of a <literal>List</>, and will be delivered to
|
||||
<function>BeginForeignModify</> during the execution stage.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
<literal>root</> is the planner's global information about the query.
|
||||
<literal>plan</> is the <structname>ModifyTable</> plan node, which is
|
||||
complete except for the <structfield>fdwPrivLists</> field.
|
||||
<literal>resultRelation</> identifies the target foreign table by its
|
||||
rangetable index. <literal>subplan_index</> identifies which target of
|
||||
the <structname>ModifyTable</> plan node this is, counting from zero;
|
||||
use this if you want to index into <literal>node->plans</> or other
|
||||
substructure of the <literal>plan</> node.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
See <xref linkend="fdw-planning"> for additional information.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
If the <function>PlanForeignModify</> pointer is set to
|
||||
<literal>NULL</>, no additional plan-time actions are taken, and the
|
||||
<literal>fdw_private</> list delivered to
|
||||
<function>BeginForeignModify</> will be NIL.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
<programlisting>
|
||||
void
|
||||
BeginForeignModify (ModifyTableState *mtstate,
|
||||
ResultRelInfo *rinfo,
|
||||
List *fdw_private,
|
||||
int subplan_index,
|
||||
int eflags);
|
||||
</programlisting>
|
||||
|
||||
Begin executing a foreign table modification operation. This routine is
|
||||
called during executor startup. It should perform any initialization
|
||||
needed prior to the actual table modifications. Subsequently,
|
||||
<function>ExecForeignInsert</>, <function>ExecForeignUpdate</> or
|
||||
<function>ExecForeignDelete</> will be called for each tuple to be
|
||||
inserted, updated, or deleted.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
<literal>mtstate</> is the overall state of the
|
||||
<structname>ModifyTable</> plan node being executed; global data about
|
||||
the plan and execution state is available via this structure.
|
||||
<literal>rinfo</> is the <structname>ResultRelInfo</> struct describing
|
||||
the target foreign table. (The <structfield>ri_FdwState</> field of
|
||||
<structname>ResultRelInfo</> is available for the FDW to store any
|
||||
private state it needs for this operation.)
|
||||
<literal>fdw_private</> contains the private data generated by
|
||||
<function>PlanForeignModify</>, if any.
|
||||
<literal>subplan_index</> identifies which target of
|
||||
the <structname>ModifyTable</> plan node this is.
|
||||
<literal>eflags</> contains flag bits describing the executor's
|
||||
operating mode for this plan node.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
Note that when <literal>(eflags & EXEC_FLAG_EXPLAIN_ONLY)</> is
|
||||
true, this function should not perform any externally-visible actions;
|
||||
it should only do the minimum required to make the node state valid
|
||||
for <function>ExplainForeignModify</> and <function>EndForeignModify</>.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
If the <function>BeginForeignModify</> pointer is set to
|
||||
<literal>NULL</>, no action is taken during executor startup.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
<programlisting>
|
||||
TupleTableSlot *
|
||||
ExecForeignInsert (EState *estate,
|
||||
ResultRelInfo *rinfo,
|
||||
TupleTableSlot *slot,
|
||||
TupleTableSlot *planSlot);
|
||||
</programlisting>
|
||||
|
||||
Insert one tuple into the foreign table.
|
||||
<literal>estate</> is global execution state for the query.
|
||||
<literal>rinfo</> is the <structname>ResultRelInfo</> struct describing
|
||||
the target foreign table.
|
||||
<literal>slot</> contains the tuple to be inserted; it will match the
|
||||
rowtype definition of the foreign table.
|
||||
<literal>planSlot</> contains the tuple that was generated by the
|
||||
<structname>ModifyTable</> plan node's subplan; it differs from
|
||||
<literal>slot</> in possibly containing additional <quote>junk</>
|
||||
columns. (The <literal>planSlot</> is typically of little interest
|
||||
for <command>INSERT</> cases, but is provided for completeness.)
|
||||
</para>
|
||||
|
||||
<para>
|
||||
The return value is either a slot containing the data that was actually
|
||||
inserted (this might differ from the data supplied, for example as a
|
||||
result of trigger actions), or NULL if no row was actually inserted
|
||||
(again, typically as a result of triggers). The passed-in
|
||||
<literal>slot</> can be re-used for this purpose.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
The data in the returned slot is used only if the <command>INSERT</>
|
||||
query has a <literal>RETURNING</> clause. Hence, the FDW could choose
|
||||
to optimize away returning some or all columns depending on the contents
|
||||
of the <literal>RETURNING</> clause. However, some slot must be
|
||||
returned to indicate success, or the query's reported rowcount will be
|
||||
wrong.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
If the <function>ExecForeignInsert</> pointer is set to
|
||||
<literal>NULL</>, attempts to insert into the foreign table will fail
|
||||
with an error message.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
<programlisting>
|
||||
TupleTableSlot *
|
||||
ExecForeignUpdate (EState *estate,
|
||||
ResultRelInfo *rinfo,
|
||||
TupleTableSlot *slot,
|
||||
TupleTableSlot *planSlot);
|
||||
</programlisting>
|
||||
|
||||
Update one tuple in the foreign table.
|
||||
<literal>estate</> is global execution state for the query.
|
||||
<literal>rinfo</> is the <structname>ResultRelInfo</> struct describing
|
||||
the target foreign table.
|
||||
<literal>slot</> contains the new data for the tuple; it will match the
|
||||
rowtype definition of the foreign table.
|
||||
<literal>planSlot</> contains the tuple that was generated by the
|
||||
<structname>ModifyTable</> plan node's subplan; it differs from
|
||||
<literal>slot</> in possibly containing additional <quote>junk</>
|
||||
columns. In particular, any junk columns that were requested by
|
||||
<function>AddForeignUpdateTargets</> will be available from this slot.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
The return value is either a slot containing the row as it was actually
|
||||
updated (this might differ from the data supplied, for example as a
|
||||
result of trigger actions), or NULL if no row was actually updated
|
||||
(again, typically as a result of triggers). The passed-in
|
||||
<literal>slot</> can be re-used for this purpose.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
The data in the returned slot is used only if the <command>UPDATE</>
|
||||
query has a <literal>RETURNING</> clause. Hence, the FDW could choose
|
||||
to optimize away returning some or all columns depending on the contents
|
||||
of the <literal>RETURNING</> clause. However, some slot must be
|
||||
returned to indicate success, or the query's reported rowcount will be
|
||||
wrong.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
If the <function>ExecForeignUpdate</> pointer is set to
|
||||
<literal>NULL</>, attempts to update the foreign table will fail
|
||||
with an error message.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
<programlisting>
|
||||
TupleTableSlot *
|
||||
ExecForeignDelete (EState *estate,
|
||||
ResultRelInfo *rinfo,
|
||||
TupleTableSlot *slot,
|
||||
TupleTableSlot *planSlot);
|
||||
</programlisting>
|
||||
|
||||
Delete one tuple from the foreign table.
|
||||
<literal>estate</> is global execution state for the query.
|
||||
<literal>rinfo</> is the <structname>ResultRelInfo</> struct describing
|
||||
the target foreign table.
|
||||
<literal>slot</> contains nothing useful upon call, but can be used to
|
||||
hold the returned tuple.
|
||||
<literal>planSlot</> contains the tuple that was generated by the
|
||||
<structname>ModifyTable</> plan node's subplan; in particular, it will
|
||||
carry any junk columns that were requested by
|
||||
<function>AddForeignUpdateTargets</>. The junk column(s) must be used
|
||||
to identify the tuple to be deleted.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
The return value is either a slot containing the row that was deleted,
|
||||
or NULL if no row was deleted (typically as a result of triggers). The
|
||||
passed-in <literal>slot</> can be used to hold the tuple to be returned.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
The data in the returned slot is used only if the <command>DELETE</>
|
||||
query has a <literal>RETURNING</> clause. Hence, the FDW could choose
|
||||
to optimize away returning some or all columns depending on the contents
|
||||
of the <literal>RETURNING</> clause. However, some slot must be
|
||||
returned to indicate success, or the query's reported rowcount will be
|
||||
wrong.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
If the <function>ExecForeignDelete</> pointer is set to
|
||||
<literal>NULL</>, attempts to delete from the foreign table will fail
|
||||
with an error message.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
<programlisting>
|
||||
void
|
||||
EndForeignModify (EState *estate,
|
||||
ResultRelInfo *rinfo);
|
||||
</programlisting>
|
||||
|
||||
End the table update 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>
|
||||
If the <function>EndForeignModify</> pointer is set to
|
||||
<literal>NULL</>, no action is taken during executor shutdown.
|
||||
</para>
|
||||
|
||||
</sect2>
|
||||
|
||||
<sect2 id="fdw-callbacks-explain">
|
||||
<title>FDW Routines for <command>EXPLAIN</></title>
|
||||
|
||||
<para>
|
||||
<programlisting>
|
||||
void
|
||||
ExplainForeignScan (ForeignScanState *node,
|
||||
ExplainState *es);
|
||||
</programlisting>
|
||||
|
||||
Print additional <command>EXPLAIN</> output for a foreign table scan.
|
||||
This function can call <function>ExplainPropertyText</> and
|
||||
related functions to add fields to the <command>EXPLAIN</> output.
|
||||
The flag fields in <literal>es</> can be used to determine what to
|
||||
print, and the state of the <structname>ForeignScanState</> node
|
||||
can be inspected to provide run-time statistics in the <command>EXPLAIN
|
||||
ANALYZE</> case.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
If the <function>ExplainForeignScan</> pointer is set to
|
||||
<literal>NULL</>, no additional information is printed during
|
||||
<command>EXPLAIN</>.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
<programlisting>
|
||||
void
|
||||
ExplainForeignModify (ModifyTableState *mtstate,
|
||||
ResultRelInfo *rinfo,
|
||||
List *fdw_private,
|
||||
int subplan_index,
|
||||
struct ExplainState *es);
|
||||
</programlisting>
|
||||
|
||||
Print additional <command>EXPLAIN</> output for a foreign table update.
|
||||
This function can call <function>ExplainPropertyText</> and
|
||||
related functions to add fields to the <command>EXPLAIN</> output.
|
||||
The flag fields in <literal>es</> can be used to determine what to
|
||||
print, and the state of the <structname>ModifyTableState</> node
|
||||
can be inspected to provide run-time statistics in the <command>EXPLAIN
|
||||
ANALYZE</> case. The first four arguments are the same as for
|
||||
<function>BeginForeignModify</>.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
If the <function>ExplainForeignModify</> pointer is set to
|
||||
<literal>NULL</>, no additional information is printed during
|
||||
<command>EXPLAIN</>.
|
||||
</para>
|
||||
|
||||
</sect2>
|
||||
|
||||
<sect2 id="fdw-callbacks-analyze">
|
||||
<title>FDW Routines for <command>ANALYZE</></title>
|
||||
|
||||
<para>
|
||||
<programlisting>
|
||||
bool
|
||||
|
@ -291,6 +637,9 @@ AnalyzeForeignTable (Relation relation,
|
|||
to a function that will collect sample rows from the table in
|
||||
<parameter>func</>, plus the estimated size of the table in pages in
|
||||
<parameter>totalpages</>. Otherwise, return <literal>false</>.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
If the FDW does not support collecting statistics for any tables, the
|
||||
<function>AnalyzeForeignTable</> pointer can be set to <literal>NULL</>.
|
||||
</para>
|
||||
|
@ -314,11 +663,7 @@ AcquireSampleRowsFunc (Relation relation, int elevel,
|
|||
if the FDW does not have any concept of dead rows.)
|
||||
</para>
|
||||
|
||||
<para>
|
||||
The <structname>FdwRoutine</> struct type is declared in
|
||||
<filename>src/include/foreign/fdwapi.h</>, which see for additional
|
||||
details.
|
||||
</para>
|
||||
</sect2>
|
||||
|
||||
</sect1>
|
||||
|
||||
|
@ -432,9 +777,10 @@ GetForeignServerByName(const char *name, bool missing_ok);
|
|||
|
||||
<para>
|
||||
The FDW callback functions <function>GetForeignRelSize</>,
|
||||
<function>GetForeignPaths</>, and <function>GetForeignPlan</> must fit
|
||||
into the workings of the <productname>PostgreSQL</> planner. Here are
|
||||
some notes about what they must do.
|
||||
<function>GetForeignPaths</>, <function>GetForeignPlan</>, and
|
||||
<function>PlanForeignModify</> must fit into the workings of the
|
||||
<productname>PostgreSQL</> planner. Here are some notes about what
|
||||
they must do.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
|
@ -546,6 +892,33 @@ GetForeignServerByName(const char *name, bool missing_ok);
|
|||
same as for an ordinary restriction clause.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
When planning an <command>UPDATE</> or <command>DELETE</>,
|
||||
<function>PlanForeignModify</> can look up the <structname>RelOptInfo</>
|
||||
struct for the foreign table and make use of the
|
||||
<literal>baserel->fdw_private</> data previously created by the
|
||||
scan-planning functions. However, in <command>INSERT</> the target
|
||||
table is not scanned so there is no <structname>RelOptInfo</> for it.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
For an <command>UPDATE</> or <command>DELETE</> against an external data
|
||||
source that supports concurrent updates, it is recommended that the
|
||||
<literal>ForeignScan</> operation lock the rows that it fetches, perhaps
|
||||
via the equivalent of <command>SELECT FOR UPDATE</>. The FDW may also
|
||||
choose to lock rows at fetch time when the foreign table is referenced
|
||||
in a <command>SELECT FOR UPDATE/SHARE</>; if it does not, the
|
||||
<literal>FOR UPDATE</> or <literal>FOR SHARE</> option is essentially a
|
||||
no-op so far as the foreign table is concerned. This behavior may yield
|
||||
semantics slightly different from operations on local tables, where row
|
||||
locking is customarily delayed as long as possible: remote rows may get
|
||||
locked even though they subsequently fail locally-applied restriction or
|
||||
join conditions. However, matching the local semantics exactly would
|
||||
require an additional remote access for every row, and might be
|
||||
impossible anyway depending on what locking semantics the external data
|
||||
source provides.
|
||||
</para>
|
||||
|
||||
</sect1>
|
||||
|
||||
</chapter>
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
files in the server's file system. Data files must be in a format
|
||||
that can be read by <command>COPY FROM</command>;
|
||||
see <xref linkend="sql-copy"> for details.
|
||||
Access to such data files is currently read-only.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
|
@ -160,7 +161,7 @@
|
|||
|
||||
<example>
|
||||
<title id="csvlog-fdw">Create a Foreign Table for PostgreSQL CSV Logs</title>
|
||||
|
||||
|
||||
<para>
|
||||
One of the obvious uses for the <literal>file_fdw</> is to make
|
||||
the PostgreSQL activity log available as a table for querying. To
|
||||
|
@ -217,8 +218,8 @@ OPTIONS ( filename '/home/josh/9.1/data/pg_log/pglog.csv', format 'csv' );
|
|||
</para>
|
||||
|
||||
<para>
|
||||
That's it — now you can query your log directly. In production, of course,
|
||||
you would need to define some way to adjust to log rotation.
|
||||
That's it — now you can query your log directly. In production, of
|
||||
course, you would need to define some way to deal with log rotation.
|
||||
</para>
|
||||
</example>
|
||||
|
||||
|
|
|
@ -61,7 +61,10 @@
|
|||
|
||||
<para>
|
||||
Now you need only <command>SELECT</> from a foreign table to access
|
||||
the data stored in its underlying remote table.
|
||||
the data stored in its underlying remote table. You can also modify
|
||||
the remote table using <command>INSERT</>, <command>UPDATE</>, or
|
||||
<command>DELETE</>. (Of course, the remote user you have specified
|
||||
in your user mapping must have privileges to do these things.)
|
||||
</para>
|
||||
|
||||
<para>
|
||||
|
|
|
@ -117,9 +117,10 @@ CREATE FOREIGN DATA WRAPPER <replaceable class="parameter">name</replaceable>
|
|||
<title>Notes</title>
|
||||
|
||||
<para>
|
||||
At the moment, the foreign-data wrapper functionality is rudimentary.
|
||||
There is no support for updating a foreign table, and optimization of
|
||||
queries is primitive (and mostly left to the wrapper, too).
|
||||
<productname>PostgreSQL</>'s foreign-data functionality is still under
|
||||
active development. Optimization of queries is primitive (and mostly left
|
||||
to the wrapper, too). Thus, there is considerable room for future
|
||||
performance improvements.
|
||||
</para>
|
||||
</refsect1>
|
||||
|
||||
|
@ -158,7 +159,7 @@ CREATE FOREIGN DATA WRAPPER mywrapper
|
|||
9075-9 (SQL/MED), with the exception that the <literal>HANDLER</literal>
|
||||
and <literal>VALIDATOR</literal> clauses are extensions and the standard
|
||||
clauses <literal>LIBRARY</literal> and <literal>LANGUAGE</literal>
|
||||
are not implemented in PostgreSQL.
|
||||
are not implemented in <productname>PostgreSQL</>.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
|
@ -175,6 +176,7 @@ CREATE FOREIGN DATA WRAPPER mywrapper
|
|||
<member><xref linkend="sql-dropforeigndatawrapper"></member>
|
||||
<member><xref linkend="sql-createserver"></member>
|
||||
<member><xref linkend="sql-createusermapping"></member>
|
||||
<member><xref linkend="sql-createforeigntable"></member>
|
||||
</simplelist>
|
||||
</refsect1>
|
||||
|
||||
|
|
|
@ -2121,17 +2121,10 @@ CopyFrom(CopyState cstate)
|
|||
* here that basically duplicated execUtils.c ...)
|
||||
*/
|
||||
resultRelInfo = makeNode(ResultRelInfo);
|
||||
resultRelInfo->ri_RangeTableIndex = 1; /* dummy */
|
||||
resultRelInfo->ri_RelationDesc = cstate->rel;
|
||||
resultRelInfo->ri_TrigDesc = CopyTriggerDesc(cstate->rel->trigdesc);
|
||||
if (resultRelInfo->ri_TrigDesc)
|
||||
{
|
||||
resultRelInfo->ri_TrigFunctions = (FmgrInfo *)
|
||||
palloc0(resultRelInfo->ri_TrigDesc->numtriggers * sizeof(FmgrInfo));
|
||||
resultRelInfo->ri_TrigWhenExprs = (List **)
|
||||
palloc0(resultRelInfo->ri_TrigDesc->numtriggers * sizeof(List *));
|
||||
}
|
||||
resultRelInfo->ri_TrigInstrument = NULL;
|
||||
InitResultRelInfo(resultRelInfo,
|
||||
cstate->rel,
|
||||
1, /* dummy rangetable index */
|
||||
0);
|
||||
|
||||
ExecOpenIndices(resultRelInfo);
|
||||
|
||||
|
|
|
@ -90,6 +90,7 @@ static void ExplainIndexScanDetails(Oid indexid, ScanDirection indexorderdir,
|
|||
static void ExplainScanTarget(Scan *plan, ExplainState *es);
|
||||
static void ExplainModifyTarget(ModifyTable *plan, ExplainState *es);
|
||||
static void ExplainTargetRel(Plan *plan, Index rti, ExplainState *es);
|
||||
static void show_modifytable_info(ModifyTableState *mtstate, ExplainState *es);
|
||||
static void ExplainMemberNodes(List *plans, PlanState **planstates,
|
||||
List *ancestors, ExplainState *es);
|
||||
static void ExplainSubPlans(List *plans, List *ancestors,
|
||||
|
@ -1341,6 +1342,9 @@ ExplainNode(PlanState *planstate, List *ancestors,
|
|||
show_instrumentation_count("Rows Removed by Filter", 1,
|
||||
planstate, es);
|
||||
break;
|
||||
case T_ModifyTable:
|
||||
show_modifytable_info((ModifyTableState *) planstate, es);
|
||||
break;
|
||||
case T_Hash:
|
||||
show_hash_info((HashState *) planstate, es);
|
||||
break;
|
||||
|
@ -1840,7 +1844,8 @@ show_foreignscan_info(ForeignScanState *fsstate, ExplainState *es)
|
|||
FdwRoutine *fdwroutine = fsstate->fdwroutine;
|
||||
|
||||
/* Let the FDW emit whatever fields it wants */
|
||||
fdwroutine->ExplainForeignScan(fsstate, es);
|
||||
if (fdwroutine->ExplainForeignScan != NULL)
|
||||
fdwroutine->ExplainForeignScan(fsstate, es);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -2036,6 +2041,34 @@ ExplainTargetRel(Plan *plan, Index rti, ExplainState *es)
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Show extra information for a ModifyTable node
|
||||
*/
|
||||
static void
|
||||
show_modifytable_info(ModifyTableState *mtstate, ExplainState *es)
|
||||
{
|
||||
FdwRoutine *fdwroutine = mtstate->resultRelInfo->ri_FdwRoutine;
|
||||
|
||||
/*
|
||||
* If the first target relation is a foreign table, call its FDW to
|
||||
* display whatever additional fields it wants to. For now, we ignore the
|
||||
* possibility of other targets being foreign tables, although the API for
|
||||
* ExplainForeignModify is designed to allow them to be processed.
|
||||
*/
|
||||
if (fdwroutine != NULL &&
|
||||
fdwroutine->ExplainForeignModify != NULL)
|
||||
{
|
||||
ModifyTable *node = (ModifyTable *) mtstate->ps.plan;
|
||||
List *fdw_private = (List *) linitial(node->fdwPrivLists);
|
||||
|
||||
fdwroutine->ExplainForeignModify(mtstate,
|
||||
mtstate->resultRelInfo,
|
||||
fdw_private,
|
||||
0,
|
||||
es);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Explain the constituent plans of a ModifyTable, Append, MergeAppend,
|
||||
* BitmapAnd, or BitmapOr node.
|
||||
|
|
|
@ -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"
|
||||
|
@ -1005,6 +1006,7 @@ void
|
|||
CheckValidResultRel(Relation resultRel, CmdType operation)
|
||||
{
|
||||
TriggerDesc *trigDesc = resultRel->trigdesc;
|
||||
FdwRoutine *fdwroutine;
|
||||
|
||||
switch (resultRel->rd_rel->relkind)
|
||||
{
|
||||
|
@ -1069,10 +1071,35 @@ CheckValidResultRel(Relation resultRel, CmdType operation)
|
|||
RelationGetRelationName(resultRel))));
|
||||
break;
|
||||
case RELKIND_FOREIGN_TABLE:
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||
errmsg("cannot change foreign table \"%s\"",
|
||||
RelationGetRelationName(resultRel))));
|
||||
/* Okay only if the FDW supports it */
|
||||
fdwroutine = GetFdwRoutineForRelation(resultRel, false);
|
||||
switch (operation)
|
||||
{
|
||||
case CMD_INSERT:
|
||||
if (fdwroutine->ExecForeignInsert == NULL)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("cannot insert into foreign table \"%s\"",
|
||||
RelationGetRelationName(resultRel))));
|
||||
break;
|
||||
case CMD_UPDATE:
|
||||
if (fdwroutine->ExecForeignUpdate == NULL)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("cannot update foreign table \"%s\"",
|
||||
RelationGetRelationName(resultRel))));
|
||||
break;
|
||||
case CMD_DELETE:
|
||||
if (fdwroutine->ExecForeignDelete == NULL)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("cannot delete from foreign table \"%s\"",
|
||||
RelationGetRelationName(resultRel))));
|
||||
break;
|
||||
default:
|
||||
elog(ERROR, "unrecognized CmdType: %d", (int) operation);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ereport(ERROR,
|
||||
|
@ -1126,7 +1153,7 @@ CheckValidRowMarkRel(Relation rel, RowMarkType markType)
|
|||
RelationGetRelationName(rel))));
|
||||
break;
|
||||
case RELKIND_FOREIGN_TABLE:
|
||||
/* Perhaps we can support this someday, but not today */
|
||||
/* Should not get here */
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
||||
errmsg("cannot lock rows in foreign table \"%s\"",
|
||||
|
@ -1180,6 +1207,11 @@ InitResultRelInfo(ResultRelInfo *resultRelInfo,
|
|||
resultRelInfo->ri_TrigWhenExprs = NULL;
|
||||
resultRelInfo->ri_TrigInstrument = NULL;
|
||||
}
|
||||
if (resultRelationDesc->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
|
||||
resultRelInfo->ri_FdwRoutine = GetFdwRoutineForRelation(resultRelationDesc, true);
|
||||
else
|
||||
resultRelInfo->ri_FdwRoutine = NULL;
|
||||
resultRelInfo->ri_FdwState = NULL;
|
||||
resultRelInfo->ri_ConstraintExprs = NULL;
|
||||
resultRelInfo->ri_junkFilter = NULL;
|
||||
resultRelInfo->ri_projectReturning = NULL;
|
||||
|
|
|
@ -147,7 +147,8 @@ ExecInitForeignScan(ForeignScan *node, EState *estate, int eflags)
|
|||
scanstate->ss.ss_currentRelation = currentRelation;
|
||||
|
||||
/*
|
||||
* get the scan type from the relation descriptor.
|
||||
* get the scan type from the relation descriptor. (XXX at some point we
|
||||
* might want to let the FDW editorialize on the scan tupdesc.)
|
||||
*/
|
||||
ExecAssignScanType(&scanstate->ss, RelationGetDescr(currentRelation));
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
@ -1243,6 +1345,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
|
||||
*/
|
||||
|
|
|
@ -179,6 +179,7 @@ _copyModifyTable(const ModifyTable *from)
|
|||
COPY_SCALAR_FIELD(resultRelIndex);
|
||||
COPY_NODE_FIELD(plans);
|
||||
COPY_NODE_FIELD(returningLists);
|
||||
COPY_NODE_FIELD(fdwPrivLists);
|
||||
COPY_NODE_FIELD(rowMarks);
|
||||
COPY_SCALAR_FIELD(epqParam);
|
||||
|
||||
|
|
|
@ -333,6 +333,7 @@ _outModifyTable(StringInfo str, const ModifyTable *node)
|
|||
WRITE_INT_FIELD(resultRelIndex);
|
||||
WRITE_NODE_FIELD(plans);
|
||||
WRITE_NODE_FIELD(returningLists);
|
||||
WRITE_NODE_FIELD(fdwPrivLists);
|
||||
WRITE_NODE_FIELD(rowMarks);
|
||||
WRITE_INT_FIELD(epqParam);
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include <math.h>
|
||||
|
||||
#include "access/skey.h"
|
||||
#include "catalog/pg_class.h"
|
||||
#include "foreign/fdwapi.h"
|
||||
#include "miscadmin.h"
|
||||
#include "nodes/makefuncs.h"
|
||||
|
@ -4695,7 +4696,8 @@ make_result(PlannerInfo *root,
|
|||
* to make it look better sometime.
|
||||
*/
|
||||
ModifyTable *
|
||||
make_modifytable(CmdType operation, bool canSetTag,
|
||||
make_modifytable(PlannerInfo *root,
|
||||
CmdType operation, bool canSetTag,
|
||||
List *resultRelations,
|
||||
List *subplans, List *returningLists,
|
||||
List *rowMarks, int epqParam)
|
||||
|
@ -4703,7 +4705,10 @@ make_modifytable(CmdType operation, bool canSetTag,
|
|||
ModifyTable *node = makeNode(ModifyTable);
|
||||
Plan *plan = &node->plan;
|
||||
double total_size;
|
||||
List *fdw_private_list;
|
||||
ListCell *subnode;
|
||||
ListCell *lc;
|
||||
int i;
|
||||
|
||||
Assert(list_length(resultRelations) == list_length(subplans));
|
||||
Assert(returningLists == NIL ||
|
||||
|
@ -4746,6 +4751,53 @@ make_modifytable(CmdType operation, bool canSetTag,
|
|||
node->rowMarks = rowMarks;
|
||||
node->epqParam = epqParam;
|
||||
|
||||
/*
|
||||
* For each result relation that is a foreign table, allow the FDW to
|
||||
* construct private plan data, and accumulate it all into a list.
|
||||
*/
|
||||
fdw_private_list = NIL;
|
||||
i = 0;
|
||||
foreach(lc, resultRelations)
|
||||
{
|
||||
Index rti = lfirst_int(lc);
|
||||
FdwRoutine *fdwroutine;
|
||||
List *fdw_private;
|
||||
|
||||
/*
|
||||
* If possible, we want to get the FdwRoutine from our RelOptInfo for
|
||||
* the table. But sometimes we don't have a RelOptInfo and must get
|
||||
* it the hard way. (In INSERT, the target relation is not scanned,
|
||||
* so it's not a baserel; and there are also corner cases for
|
||||
* updatable views where the target rel isn't a baserel.)
|
||||
*/
|
||||
if (rti < root->simple_rel_array_size &&
|
||||
root->simple_rel_array[rti] != NULL)
|
||||
{
|
||||
RelOptInfo *resultRel = root->simple_rel_array[rti];
|
||||
|
||||
fdwroutine = resultRel->fdwroutine;
|
||||
}
|
||||
else
|
||||
{
|
||||
RangeTblEntry *rte = planner_rt_fetch(rti, root);
|
||||
|
||||
Assert(rte->rtekind == RTE_RELATION);
|
||||
if (rte->relkind == RELKIND_FOREIGN_TABLE)
|
||||
fdwroutine = GetFdwRoutineByRelId(rte->relid);
|
||||
else
|
||||
fdwroutine = NULL;
|
||||
}
|
||||
|
||||
if (fdwroutine != NULL &&
|
||||
fdwroutine->PlanForeignModify != NULL)
|
||||
fdw_private = fdwroutine->PlanForeignModify(root, node, rti, i);
|
||||
else
|
||||
fdw_private = NIL;
|
||||
fdw_private_list = lappend(fdw_private_list, fdw_private);
|
||||
i++;
|
||||
}
|
||||
node->fdwPrivLists = fdw_private_list;
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
|
|
|
@ -571,7 +571,8 @@ subquery_planner(PlannerGlobal *glob, Query *parse,
|
|||
else
|
||||
rowMarks = root->rowMarks;
|
||||
|
||||
plan = (Plan *) make_modifytable(parse->commandType,
|
||||
plan = (Plan *) make_modifytable(root,
|
||||
parse->commandType,
|
||||
parse->canSetTag,
|
||||
list_make1_int(parse->resultRelation),
|
||||
list_make1(plan),
|
||||
|
@ -964,7 +965,8 @@ inheritance_planner(PlannerInfo *root)
|
|||
rowMarks = root->rowMarks;
|
||||
|
||||
/* And last, tack on a ModifyTable node to do the UPDATE/DELETE work */
|
||||
return (Plan *) make_modifytable(parse->commandType,
|
||||
return (Plan *) make_modifytable(root,
|
||||
parse->commandType,
|
||||
parse->canSetTag,
|
||||
resultRelations,
|
||||
subplans,
|
||||
|
@ -2035,6 +2037,15 @@ preprocess_rowmarks(PlannerInfo *root)
|
|||
if (rte->rtekind != RTE_RELATION)
|
||||
continue;
|
||||
|
||||
/*
|
||||
* Similarly, ignore RowMarkClauses for foreign tables; foreign tables
|
||||
* will instead get ROW_MARK_COPY items in the next loop. (FDWs might
|
||||
* choose to do something special while fetching their rows, but that
|
||||
* is of no concern here.)
|
||||
*/
|
||||
if (rte->relkind == RELKIND_FOREIGN_TABLE)
|
||||
continue;
|
||||
|
||||
rels = bms_del_member(rels, rc->rti);
|
||||
|
||||
newrc = makeNode(PlanRowMark);
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
* For INSERT and UPDATE queries, the targetlist must contain an entry for
|
||||
* each attribute of the target relation in the correct order. For all query
|
||||
* types, we may need to add junk tlist entries for Vars used in the RETURNING
|
||||
* list and row ID information needed for EvalPlanQual checking.
|
||||
* list and row ID information needed for SELECT FOR UPDATE locking and/or
|
||||
* EvalPlanQual checking.
|
||||
*
|
||||
* NOTE: the rewriter's rewriteTargetListIU and rewriteTargetListUD
|
||||
* routines also do preprocessing of the targetlist. The division of labor
|
||||
|
|
|
@ -2164,7 +2164,7 @@ transformCreateTableAsStmt(ParseState *pstate, CreateTableAsStmt *stmt)
|
|||
|
||||
|
||||
/*
|
||||
* Check for features that are not supported together with FOR [KEY] UPDATE/SHARE.
|
||||
* Check for features that are not supported with FOR [KEY] UPDATE/SHARE.
|
||||
*
|
||||
* exported so planner can check again after rewriting, query pullup, etc
|
||||
*/
|
||||
|
@ -2239,9 +2239,6 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
|
|||
switch (rte->rtekind)
|
||||
{
|
||||
case RTE_RELATION:
|
||||
/* ignore foreign tables */
|
||||
if (rte->relkind == RELKIND_FOREIGN_TABLE)
|
||||
break;
|
||||
applyLockingClause(qry, i,
|
||||
lc->strength, lc->noWait, pushedDown);
|
||||
rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
|
||||
|
@ -2251,7 +2248,7 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
|
|||
lc->strength, lc->noWait, pushedDown);
|
||||
|
||||
/*
|
||||
* FOR [KEY] UPDATE/SHARE of subquery is propagated to all of
|
||||
* FOR UPDATE/SHARE of subquery is propagated to all of
|
||||
* subquery's rels, too. We could do this later (based on
|
||||
* the marking of the subquery RTE) but it is convenient
|
||||
* to have local knowledge in each query level about which
|
||||
|
@ -2291,12 +2288,6 @@ transformLockingClause(ParseState *pstate, Query *qry, LockingClause *lc,
|
|||
switch (rte->rtekind)
|
||||
{
|
||||
case RTE_RELATION:
|
||||
if (rte->relkind == RELKIND_FOREIGN_TABLE)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("row-level locks cannot be used with foreign table \"%s\"",
|
||||
rte->eref->aliasname),
|
||||
parser_errposition(pstate, thisrel->location)));
|
||||
applyLockingClause(qry, i,
|
||||
lc->strength, lc->noWait,
|
||||
pushedDown);
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
#include "access/sysattr.h"
|
||||
#include "catalog/pg_type.h"
|
||||
#include "commands/trigger.h"
|
||||
#include "foreign/fdwapi.h"
|
||||
#include "nodes/makefuncs.h"
|
||||
#include "nodes/nodeFuncs.h"
|
||||
#include "parser/analyze.h"
|
||||
|
@ -1156,6 +1157,7 @@ rewriteValuesRTE(RangeTblEntry *rte, Relation target_relation, List *attrnos)
|
|||
* is a regular table, the junk TLE emits the ctid attribute of the original
|
||||
* row. When the target relation is a view, there is no ctid, so we instead
|
||||
* emit a whole-row Var that will contain the "old" values of the view row.
|
||||
* If it's a foreign table, we let the FDW decide what to add.
|
||||
*
|
||||
* For UPDATE queries, this is applied after rewriteTargetListIU. The
|
||||
* ordering isn't actually critical at the moment.
|
||||
|
@ -1183,6 +1185,21 @@ rewriteTargetListUD(Query *parsetree, RangeTblEntry *target_rte,
|
|||
|
||||
attrname = "ctid";
|
||||
}
|
||||
else if (target_relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
|
||||
{
|
||||
/*
|
||||
* Let the foreign table's FDW add whatever junk TLEs it wants.
|
||||
*/
|
||||
FdwRoutine *fdwroutine;
|
||||
|
||||
fdwroutine = GetFdwRoutineForRelation(target_relation, false);
|
||||
|
||||
if (fdwroutine->AddForeignUpdateTargets != NULL)
|
||||
fdwroutine->AddForeignUpdateTargets(parsetree, target_rte,
|
||||
target_relation);
|
||||
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
|
@ -1444,17 +1461,13 @@ markQueryForLocking(Query *qry, Node *jtnode,
|
|||
|
||||
if (rte->rtekind == RTE_RELATION)
|
||||
{
|
||||
/* ignore foreign tables */
|
||||
if (rte->relkind != RELKIND_FOREIGN_TABLE)
|
||||
{
|
||||
applyLockingClause(qry, rti, strength, noWait, pushedDown);
|
||||
rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
|
||||
}
|
||||
applyLockingClause(qry, rti, strength, noWait, pushedDown);
|
||||
rte->requiredPerms |= ACL_SELECT_FOR_UPDATE;
|
||||
}
|
||||
else if (rte->rtekind == RTE_SUBQUERY)
|
||||
{
|
||||
applyLockingClause(qry, rti, strength, noWait, pushedDown);
|
||||
/* FOR [KEY] UPDATE/SHARE of subquery is propagated to subquery's rels */
|
||||
/* FOR UPDATE/SHARE of subquery is propagated to subquery's rels */
|
||||
markQueryForLocking(rte->subquery, (Node *) rte->subquery->jointree,
|
||||
strength, noWait, true);
|
||||
}
|
||||
|
|
|
@ -38,9 +38,6 @@ typedef ForeignScan *(*GetForeignPlan_function) (PlannerInfo *root,
|
|||
List *tlist,
|
||||
List *scan_clauses);
|
||||
|
||||
typedef void (*ExplainForeignScan_function) (ForeignScanState *node,
|
||||
struct ExplainState *es);
|
||||
|
||||
typedef void (*BeginForeignScan_function) (ForeignScanState *node,
|
||||
int eflags);
|
||||
|
||||
|
@ -50,6 +47,48 @@ typedef void (*ReScanForeignScan_function) (ForeignScanState *node);
|
|||
|
||||
typedef void (*EndForeignScan_function) (ForeignScanState *node);
|
||||
|
||||
typedef void (*AddForeignUpdateTargets_function) (Query *parsetree,
|
||||
RangeTblEntry *target_rte,
|
||||
Relation target_relation);
|
||||
|
||||
typedef List *(*PlanForeignModify_function) (PlannerInfo *root,
|
||||
ModifyTable *plan,
|
||||
Index resultRelation,
|
||||
int subplan_index);
|
||||
|
||||
typedef void (*BeginForeignModify_function) (ModifyTableState *mtstate,
|
||||
ResultRelInfo *rinfo,
|
||||
List *fdw_private,
|
||||
int subplan_index,
|
||||
int eflags);
|
||||
|
||||
typedef TupleTableSlot *(*ExecForeignInsert_function) (EState *estate,
|
||||
ResultRelInfo *rinfo,
|
||||
TupleTableSlot *slot,
|
||||
TupleTableSlot *planSlot);
|
||||
|
||||
typedef TupleTableSlot *(*ExecForeignUpdate_function) (EState *estate,
|
||||
ResultRelInfo *rinfo,
|
||||
TupleTableSlot *slot,
|
||||
TupleTableSlot *planSlot);
|
||||
|
||||
typedef TupleTableSlot *(*ExecForeignDelete_function) (EState *estate,
|
||||
ResultRelInfo *rinfo,
|
||||
TupleTableSlot *slot,
|
||||
TupleTableSlot *planSlot);
|
||||
|
||||
typedef void (*EndForeignModify_function) (EState *estate,
|
||||
ResultRelInfo *rinfo);
|
||||
|
||||
typedef void (*ExplainForeignScan_function) (ForeignScanState *node,
|
||||
struct ExplainState *es);
|
||||
|
||||
typedef void (*ExplainForeignModify_function) (ModifyTableState *mtstate,
|
||||
ResultRelInfo *rinfo,
|
||||
List *fdw_private,
|
||||
int subplan_index,
|
||||
struct ExplainState *es);
|
||||
|
||||
typedef int (*AcquireSampleRowsFunc) (Relation relation, int elevel,
|
||||
HeapTuple *rows, int targrows,
|
||||
double *totalrows,
|
||||
|
@ -73,22 +112,34 @@ typedef struct FdwRoutine
|
|||
{
|
||||
NodeTag type;
|
||||
|
||||
/*
|
||||
* These functions are required.
|
||||
*/
|
||||
/* Functions for scanning foreign tables */
|
||||
GetForeignRelSize_function GetForeignRelSize;
|
||||
GetForeignPaths_function GetForeignPaths;
|
||||
GetForeignPlan_function GetForeignPlan;
|
||||
ExplainForeignScan_function ExplainForeignScan;
|
||||
BeginForeignScan_function BeginForeignScan;
|
||||
IterateForeignScan_function IterateForeignScan;
|
||||
ReScanForeignScan_function ReScanForeignScan;
|
||||
EndForeignScan_function EndForeignScan;
|
||||
|
||||
/*
|
||||
* These functions are optional. Set the pointer to NULL for any that are
|
||||
* not provided.
|
||||
* Remaining functions are optional. Set the pointer to NULL for any that
|
||||
* are not provided.
|
||||
*/
|
||||
|
||||
/* Functions for updating foreign tables */
|
||||
AddForeignUpdateTargets_function AddForeignUpdateTargets;
|
||||
PlanForeignModify_function PlanForeignModify;
|
||||
BeginForeignModify_function BeginForeignModify;
|
||||
ExecForeignInsert_function ExecForeignInsert;
|
||||
ExecForeignUpdate_function ExecForeignUpdate;
|
||||
ExecForeignDelete_function ExecForeignDelete;
|
||||
EndForeignModify_function EndForeignModify;
|
||||
|
||||
/* Support functions for EXPLAIN */
|
||||
ExplainForeignScan_function ExplainForeignScan;
|
||||
ExplainForeignModify_function ExplainForeignModify;
|
||||
|
||||
/* Support functions for ANALYZE */
|
||||
AnalyzeForeignTable_function AnalyzeForeignTable;
|
||||
} FdwRoutine;
|
||||
|
||||
|
|
|
@ -270,7 +270,8 @@ typedef struct ProjectionInfo
|
|||
* 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
|
||||
* (execMain.c stores the "ctid" attno here).
|
||||
* (nodeModifyTable.c keeps the "ctid" or "wholerow"
|
||||
* attno here).
|
||||
* ----------------
|
||||
*/
|
||||
typedef struct JunkFilter
|
||||
|
@ -300,6 +301,8 @@ typedef struct JunkFilter
|
|||
* TrigFunctions cached lookup info for trigger functions
|
||||
* TrigWhenExprs array of trigger WHEN expr states
|
||||
* TrigInstrument optional runtime measurements for triggers
|
||||
* FdwRoutine FDW callback functions, if foreign table
|
||||
* FdwState available to save private state of FDW
|
||||
* ConstraintExprs array of constraint-checking expr states
|
||||
* junkFilter for removing junk attributes from tuples
|
||||
* projectReturning for computing a RETURNING list
|
||||
|
@ -317,6 +320,8 @@ typedef struct ResultRelInfo
|
|||
FmgrInfo *ri_TrigFunctions;
|
||||
List **ri_TrigWhenExprs;
|
||||
Instrumentation *ri_TrigInstrument;
|
||||
struct FdwRoutine *ri_FdwRoutine;
|
||||
void *ri_FdwState;
|
||||
List **ri_ConstraintExprs;
|
||||
JunkFilter *ri_junkFilter;
|
||||
ProjectionInfo *ri_projectReturning;
|
||||
|
|
|
@ -173,6 +173,7 @@ typedef struct ModifyTable
|
|||
int resultRelIndex; /* index of first resultRel in plan's list */
|
||||
List *plans; /* plan(s) producing source data */
|
||||
List *returningLists; /* per-target-table RETURNING tlists */
|
||||
List *fdwPrivLists; /* per-target-table FDW private data lists */
|
||||
List *rowMarks; /* PlanRowMarks (non-locking only) */
|
||||
int epqParam; /* ID of Param for EvalPlanQual re-eval */
|
||||
} ModifyTable;
|
||||
|
@ -752,13 +753,32 @@ typedef struct Limit
|
|||
* RowMarkType -
|
||||
* enums for types of row-marking operations
|
||||
*
|
||||
* When doing UPDATE, DELETE, or SELECT FOR [KEY] UPDATE/SHARE, we have to uniquely
|
||||
* The first four of these values represent different lock strengths that
|
||||
* we can take on tuples according to SELECT FOR [KEY] UPDATE/SHARE requests.
|
||||
* We only support these on regular tables. For foreign tables, any locking
|
||||
* that might be done for these requests must happen during the initial row
|
||||
* fetch; there is no mechanism for going back to lock a row later (and thus
|
||||
* no need for EvalPlanQual machinery during updates of foreign tables).
|
||||
* This means that the semantics will be a bit different than for a local
|
||||
* table; in particular we are likely to lock more rows than would be locked
|
||||
* locally, since remote rows will be locked even if they then fail
|
||||
* locally-checked restriction or join quals. However, the alternative of
|
||||
* doing a separate remote query to lock each selected row is extremely
|
||||
* unappealing, so let's do it like this for now.
|
||||
*
|
||||
* When doing UPDATE, DELETE, or SELECT FOR UPDATE/SHARE, we have to uniquely
|
||||
* identify all the source rows, not only those from the target relations, so
|
||||
* that we can perform EvalPlanQual rechecking at need. For plain tables we
|
||||
* can just fetch the TID, the same as for a target relation. Otherwise (for
|
||||
* example for VALUES or FUNCTION scans) we have to copy the whole row value.
|
||||
* The latter is pretty inefficient but fortunately the case is not
|
||||
* performance-critical in practice.
|
||||
* can just fetch the TID, much as for a target relation; this case is
|
||||
* represented by ROW_MARK_REFERENCE. Otherwise (for example for VALUES or
|
||||
* FUNCTION scans) we have to copy the whole row value. ROW_MARK_COPY is
|
||||
* pretty inefficient, since most of the time we'll never need the data; but
|
||||
* fortunately the case is not performance-critical in practice. Note that
|
||||
* we use ROW_MARK_COPY for non-target foreign tables, even if the FDW has a
|
||||
* concept of rowid and so could theoretically support some form of
|
||||
* ROW_MARK_REFERENCE. Although copying the whole row value is inefficient,
|
||||
* it's probably still faster than doing a second remote fetch, so it doesn't
|
||||
* seem worth the extra complexity to permit ROW_MARK_REFERENCE.
|
||||
*/
|
||||
typedef enum RowMarkType
|
||||
{
|
||||
|
@ -776,10 +796,10 @@ typedef enum RowMarkType
|
|||
* PlanRowMark -
|
||||
* plan-time representation of FOR [KEY] UPDATE/SHARE clauses
|
||||
*
|
||||
* When doing UPDATE, DELETE, or SELECT FOR [KEY] UPDATE/SHARE, we create a separate
|
||||
* When doing UPDATE, DELETE, or SELECT FOR UPDATE/SHARE, we create a separate
|
||||
* PlanRowMark node for each non-target relation in the query. Relations that
|
||||
* are not specified as FOR [KEY] UPDATE/SHARE are marked ROW_MARK_REFERENCE (if
|
||||
* real tables) or ROW_MARK_COPY (if not).
|
||||
* are not specified as FOR UPDATE/SHARE are marked ROW_MARK_REFERENCE (if
|
||||
* regular tables) or ROW_MARK_COPY (if not).
|
||||
*
|
||||
* Initially all PlanRowMarks have rti == prti and isParent == false.
|
||||
* When the planner discovers that a relation is the root of an inheritance
|
||||
|
@ -791,7 +811,7 @@ typedef enum RowMarkType
|
|||
*
|
||||
* The planner also adds resjunk output columns to the plan that carry
|
||||
* information sufficient to identify the locked or fetched rows. For
|
||||
* tables (markType != ROW_MARK_COPY), these columns are named
|
||||
* regular tables (markType != ROW_MARK_COPY), these columns are named
|
||||
* tableoid%u OID of table
|
||||
* ctid%u TID of row
|
||||
* The tableoid column is only present for an inheritance hierarchy.
|
||||
|
|
|
@ -79,7 +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,
|
||||
extern ModifyTable *make_modifytable(PlannerInfo *root,
|
||||
CmdType operation, bool canSetTag,
|
||||
List *resultRelations, List *subplans, List *returningLists,
|
||||
List *rowMarks, int epqParam);
|
||||
extern bool is_projection_capable_plan(Plan *plan);
|
||||
|
|
Loading…
Reference in a new issue