Commit 21734d2f authored by Tom Lane's avatar Tom Lane

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.
parent 7f49a67f
......@@ -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;
bool have_wholerow;
bool first;
AttrNumber attr;
ListCell *lc;
RangeTblEntry *rte = planner_rt_fetch(baserel->relid, root);
Relation rel;
/* Collect all the attributes needed for joins or final output. */
pull_varattnos((Node *) baserel->reltargetlist, baserel->relid,
&attrs_used);
/*
* Core code already has some lock on each rel being planned, so we can
* use NoLock here.
*/
rel = heap_open(rte->relid, NoLock);
/* Add all the attributes used by local_conds. */
foreach(lc, local_conds)
{
RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
/*
* Construct SELECT list
*/
appendStringInfoString(buf, "SELECT ");
deparseTargetList(buf, root, baserel->relid, rel, attrs_used);
pull_varattnos((Node *) rinfo->clause, baserel->relid,
&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;
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");
}
/* Don't generate bad syntax if no undropped columns */
if (first)
appendStringInfo(buf, "NULL");
/*
* Construct FROM clause
* Add ctid if needed. We currently don't support retrieving any other
* system columns.
*/
appendStringInfo(buf, " FROM ");
deparseRelation(buf, rte->relid);
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)
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;
}
......
......@@ -731,3 +731,1512 @@ SELECT * FROM ft1 ORDER BY c1 LIMIT 1;
(1 row)
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;
QUERY PLAN
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Insert on public.ft2
Remote SQL: INSERT INTO "S 1"."T 1"("C 1", c2, c3) VALUES ($1, $2, $3)
-> Subquery Scan on "*SELECT*"
Output: NULL::integer, "*SELECT*"."?column?", "*SELECT*"."?column?_1", "*SELECT*"."?column?_2", NULL::timestamp with time zone, NULL::timestamp without time zone, NULL::character varying, NULL::bpchar, NULL::user_enum
-> Limit
Output: ((ft2_1.c1 + 1000)), ((ft2_1.c2 + 100)), ((ft2_1.c3 || ft2_1.c3))
-> Foreign Scan on public.ft2 ft2_1
Output: (ft2_1.c1 + 1000), (ft2_1.c2 + 100), (ft2_1.c3 || ft2_1.c3)
Remote SQL: SELECT "C 1", c2, c3, NULL, NULL, NULL, NULL, NULL FROM "S 1"."T 1"
(9 rows)
INSERT INTO ft2 (c1,c2,c3) SELECT c1+1000,c2+100, c3 || c3 FROM ft2 LIMIT 20;
INSERT INTO ft2 (c1,c2,c3)
VALUES (1101,201,'aaa'), (1102,202,'bbb'), (1103,203,'ccc') RETURNING *;
c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8
------+-----+-----+----+----+----+----+----
1101 | 201 | aaa | | | | |
1102 | 202 | bbb | | | | |
1103 | 203 | ccc | | | | |
(3 rows)
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 *;
c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8
------+-----+--------------------+------------------------------+--------------------------+----+------------+-----
7 | 407 | 00007_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo
17 | 407 | 00017_update7 | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7 | 7 | foo
27 | 407 | 00027_update7 | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7 | 7 | foo
37 | 407 | 00037_update7 | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7 | 7 | foo
47 | 407 | 00047_update7 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo
57 | 407 | 00057_update7 | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7 | 7 | foo
67 | 407 | 00067_update7 | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7 | 7 | foo
77 | 407 | 00077_update7 | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7 | 7 | foo
87 | 407 | 00087_update7 | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7 | 7 | foo
97 | 407 | 00097_update7 | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7 | 7 | foo
107 | 407 | 00107_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo
117 | 407 | 00117_update7 | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7 | 7 | foo
127 | 407 | 00127_update7 | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7 | 7 | foo
137 | 407 | 00137_update7 | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7 | 7 | foo
147 | 407 | 00147_update7 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo
157 | 407 | 00157_update7 | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7 | 7 | foo
167 | 407 | 00167_update7 | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7 | 7 | foo
177 | 407 | 00177_update7 | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7 | 7 | foo
187 | 407 | 00187_update7 | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7 | 7 | foo
197 | 407 | 00197_update7 | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7 | 7 | foo
207 | 407 | 00207_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo
217 | 407 | 00217_update7 | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7 | 7 | foo
227 | 407 | 00227_update7 | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7 | 7 | foo
237 | 407 | 00237_update7 | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7 | 7 | foo
247 | 407 | 00247_update7 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo
257 | 407 | 00257_update7 | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7 | 7 | foo
267 | 407 | 00267_update7 | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7 | 7 | foo
277 | 407 | 00277_update7 | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7 | 7 | foo
287 | 407 | 00287_update7 | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7 | 7 | foo
297 | 407 | 00297_update7 | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7 | 7 | foo
307 | 407 | 00307_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo
317 | 407 | 00317_update7 | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7 | 7 | foo
327 | 407 | 00327_update7 | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7 | 7 | foo
337 | 407 | 00337_update7 | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7 | 7 | foo
347 | 407 | 00347_update7 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo
357 | 407 | 00357_update7 | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7 | 7 | foo
367 | 407 | 00367_update7 | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7 | 7 | foo
377 | 407 | 00377_update7 | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7 | 7 | foo
387 | 407 | 00387_update7 | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7 | 7 | foo
397 | 407 | 00397_update7 | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7 | 7 | foo
407 | 407 | 00407_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo
417 | 407 | 00417_update7 | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7 | 7 | foo
427 | 407 | 00427_update7 | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7 | 7 | foo
437 | 407 | 00437_update7 | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7 | 7 | foo
447 | 407 | 00447_update7 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo
457 | 407 | 00457_update7 | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7 | 7 | foo
467 | 407 | 00467_update7 | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7 | 7 | foo
477 | 407 | 00477_update7 | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7 | 7 | foo
487 | 407 | 00487_update7 | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7 | 7 | foo
497 | 407 | 00497_update7 | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7 | 7 | foo
507 | 407 | 00507_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo
517 | 407 | 00517_update7 | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7 | 7 | foo
527 | 407 | 00527_update7 | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7 | 7 | foo
537 | 407 | 00537_update7 | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7 | 7 | foo
547 | 407 | 00547_update7 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo
557 | 407 | 00557_update7 | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7 | 7 | foo
567 | 407 | 00567_update7 | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7 | 7 | foo
577 | 407 | 00577_update7 | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7 | 7 | foo
587 | 407 | 00587_update7 | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7 | 7 | foo
597 | 407 | 00597_update7 | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7 | 7 | foo
607 | 407 | 00607_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo
617 | 407 | 00617_update7 | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7 | 7 | foo
627 | 407 | 00627_update7 | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7 | 7 | foo
637 | 407 | 00637_update7 | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7 | 7 | foo
647 | 407 | 00647_update7 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo
657 | 407 | 00657_update7 | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7 | 7 | foo
667 | 407 | 00667_update7 | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7 | 7 | foo
677 | 407 | 00677_update7 | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7 | 7 | foo
687 | 407 | 00687_update7 | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7 | 7 | foo
697 | 407 | 00697_update7 | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7 | 7 | foo
707 | 407 | 00707_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo
717 | 407 | 00717_update7 | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7 | 7 | foo
727 | 407 | 00727_update7 | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7 | 7 | foo
737 | 407 | 00737_update7 | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7 | 7 | foo
747 | 407 | 00747_update7 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo
757 | 407 | 00757_update7 | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7 | 7 | foo
767 | 407 | 00767_update7 | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7 | 7 | foo
777 | 407 | 00777_update7 | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7 | 7 | foo
787 | 407 | 00787_update7 | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7 | 7 | foo
797 | 407 | 00797_update7 | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7 | 7 | foo
807 | 407 | 00807_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo
817 | 407 | 00817_update7 | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7 | 7 | foo
827 | 407 | 00827_update7 | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7 | 7 | foo
837 | 407 | 00837_update7 | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7 | 7 | foo
847 | 407 | 00847_update7 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo
857 | 407 | 00857_update7 | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7 | 7 | foo
867 | 407 | 00867_update7 | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7 | 7 | foo
877 | 407 | 00877_update7 | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7 | 7 | foo
887 | 407 | 00887_update7 | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7 | 7 | foo
897 | 407 | 00897_update7 | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7 | 7 | foo
907 | 407 | 00907_update7 | Thu Jan 08 00:00:00 1970 PST | Thu Jan 08 00:00:00 1970 | 7 | 7 | foo
917 | 407 | 00917_update7 | Sun Jan 18 00:00:00 1970 PST | Sun Jan 18 00:00:00 1970 | 7 | 7 | foo
927 | 407 | 00927_update7 | Wed Jan 28 00:00:00 1970 PST | Wed Jan 28 00:00:00 1970 | 7 | 7 | foo
937 | 407 | 00937_update7 | Sat Feb 07 00:00:00 1970 PST | Sat Feb 07 00:00:00 1970 | 7 | 7 | foo
947 | 407 | 00947_update7 | Tue Feb 17 00:00:00 1970 PST | Tue Feb 17 00:00:00 1970 | 7 | 7 | foo
957 | 407 | 00957_update7 | Fri Feb 27 00:00:00 1970 PST | Fri Feb 27 00:00:00 1970 | 7 | 7 | foo
967 | 407 | 00967_update7 | Mon Mar 09 00:00:00 1970 PST | Mon Mar 09 00:00:00 1970 | 7 | 7 | foo
977 | 407 | 00977_update7 | Thu Mar 19 00:00:00 1970 PST | Thu Mar 19 00:00:00 1970 | 7 | 7 | foo
987 | 407 | 00987_update7 | Sun Mar 29 00:00:00 1970 PST | Sun Mar 29 00:00:00 1970 | 7 | 7 | foo
997 | 407 | 00997_update7 | Wed Apr 08 00:00:00 1970 PST | Wed Apr 08 00:00:00 1970 | 7 | 7 | foo
1007 | 507 | 0000700007_update7 | | | | |
1017 | 507 | 0001700017_update7 | | | | |
(102 rows)
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;
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------------------------
Update on public.ft2
Remote SQL: UPDATE "S 1"."T 1" SET c2 = $2, c3 = $3 WHERE ctid = $1
-> Hash Join
Output: NULL::integer, ft2.c1, (ft2.c2 + 500), (ft2.c3 || '_update9'::text), ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.ctid, ft1.*
Hash Cond: (ft2.c2 = ft1.c1)
-> Foreign Scan on public.ft2
Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.ctid
Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8, ctid FROM "S 1"."T 1" FOR UPDATE
-> Hash
Output: ft1.*, ft1.c1
-> Foreign Scan on public.ft1
Output: ft1.*, ft1.c1
Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 9))
(13 rows)
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 *;
c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8
------+-----+------------+------------------------------+--------------------------+----+------------+-----
5 | 5 | 00005 | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5 | 5 | foo
15 | 5 | 00015 | Fri Jan 16 00:00:00 1970 PST | Fri Jan 16 00:00:00 1970 | 5 | 5 | foo
25 | 5 | 00025 | Mon Jan 26 00:00:00 1970 PST | Mon Jan 26 00:00:00 1970 | 5 | 5 | foo
35 | 5 | 00035 | Thu Feb 05 00:00:00 1970 PST | Thu Feb 05 00:00:00 1970 | 5 | 5 | foo
45 | 5 | 00045 | Sun Feb 15 00:00:00 1970 PST | Sun Feb 15 00:00:00 1970 | 5 | 5 | foo
55 | 5 | 00055 | Wed Feb 25 00:00:00 1970 PST | Wed Feb 25 00:00:00 1970 | 5 | 5 | foo
65 | 5 | 00065 | Sat Mar 07 00:00:00 1970 PST | Sat Mar 07 00:00:00 1970 | 5 | 5 | foo
75 | 5 | 00075 | Tue Mar 17 00:00:00 1970 PST | Tue Mar 17 00:00:00 1970 | 5 | 5 | foo
85 | 5 | 00085 | Fri Mar 27 00:00:00 1970 PST | Fri Mar 27 00:00:00 1970 | 5 | 5 | foo
95 | 5 | 00095 | Mon Apr 06 00:00:00 1970 PST | Mon Apr 06 00:00:00 1970 | 5 | 5 | foo
105 | 5 | 00105 | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5 | 5 | foo
115 | 5 | 00115 | Fri Jan 16 00:00:00 1970 PST | Fri Jan 16 00:00:00 1970 | 5 | 5 | foo
125 | 5 | 00125 | Mon Jan 26 00:00:00 1970 PST | Mon Jan 26 00:00:00 1970 | 5 | 5 | foo
135 | 5 | 00135 | Thu Feb 05 00:00:00 1970 PST | Thu Feb 05 00:00:00 1970 | 5 | 5 | foo
145 | 5 | 00145 | Sun Feb 15 00:00:00 1970 PST | Sun Feb 15 00:00:00 1970 | 5 | 5 | foo
155 | 5 | 00155 | Wed Feb 25 00:00:00 1970 PST | Wed Feb 25 00:00:00 1970 | 5 | 5 | foo
165 | 5 | 00165 | Sat Mar 07 00:00:00 1970 PST | Sat Mar 07 00:00:00 1970 | 5 | 5 | foo
175 | 5 | 00175 | Tue Mar 17 00:00:00 1970 PST | Tue Mar 17 00:00:00 1970 | 5 | 5 | foo
185 | 5 | 00185 | Fri Mar 27 00:00:00 1970 PST | Fri Mar 27 00:00:00 1970 | 5 | 5 | foo
195 | 5 | 00195 | Mon Apr 06 00:00:00 1970 PST | Mon Apr 06 00:00:00 1970 | 5 | 5 | foo
205 | 5 | 00205 | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5 | 5 | foo
215 | 5 | 00215 | Fri Jan 16 00:00:00 1970 PST | Fri Jan 16 00:00:00 1970 | 5 | 5 | foo
225 | 5 | 00225 | Mon Jan 26 00:00:00 1970 PST | Mon Jan 26 00:00:00 1970 | 5 | 5 | foo
235 | 5 | 00235 | Thu Feb 05 00:00:00 1970 PST | Thu Feb 05 00:00:00 1970 | 5 | 5 | foo
245 | 5 | 00245 | Sun Feb 15 00:00:00 1970 PST | Sun Feb 15 00:00:00 1970 | 5 | 5 | foo
255 | 5 | 00255 | Wed Feb 25 00:00:00 1970 PST | Wed Feb 25 00:00:00 1970 | 5 | 5 | foo
265 | 5 | 00265 | Sat Mar 07 00:00:00 1970 PST | Sat Mar 07 00:00:00 1970 | 5 | 5 | foo
275 | 5 | 00275 | Tue Mar 17 00:00:00 1970 PST | Tue Mar 17 00:00:00 1970 | 5 | 5 | foo
285 | 5 | 00285 | Fri Mar 27 00:00:00 1970 PST | Fri Mar 27 00:00:00 1970 | 5 | 5 | foo
295 | 5 | 00295 | Mon Apr 06 00:00:00 1970 PST | Mon Apr 06 00:00:00 1970 | 5 | 5 | foo
305 | 5 | 00305 | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5 | 5 | foo
315 | 5 | 00315 | Fri Jan 16 00:00:00 1970 PST | Fri Jan 16 00:00:00 1970 | 5 | 5 | foo
325 | 5 | 00325 | Mon Jan 26 00:00:00 1970 PST | Mon Jan 26 00:00:00 1970 | 5 | 5 | foo
335 | 5 | 00335 | Thu Feb 05 00:00:00 1970 PST | Thu Feb 05 00:00:00 1970 | 5 | 5 | foo
345 | 5 | 00345 | Sun Feb 15 00:00:00 1970 PST | Sun Feb 15 00:00:00 1970 | 5 | 5 | foo
355 | 5 | 00355 | Wed Feb 25 00:00:00 1970 PST | Wed Feb 25 00:00:00 1970 | 5 | 5 | foo
365 | 5 | 00365 | Sat Mar 07 00:00:00 1970 PST | Sat Mar 07 00:00:00 1970 | 5 | 5 | foo
375 | 5 | 00375 | Tue Mar 17 00:00:00 1970 PST | Tue Mar 17 00:00:00 1970 | 5 | 5 | foo
385 | 5 | 00385 | Fri Mar 27 00:00:00 1970 PST | Fri Mar 27 00:00:00 1970 | 5 | 5 | foo
395 | 5 | 00395 | Mon Apr 06 00:00:00 1970 PST | Mon Apr 06 00:00:00 1970 | 5 | 5 | foo
405 | 5 | 00405 | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5 | 5 | foo
415 | 5 | 00415 | Fri Jan 16 00:00:00 1970 PST | Fri Jan 16 00:00:00 1970 | 5 | 5 | foo
425 | 5 | 00425 | Mon Jan 26 00:00:00 1970 PST | Mon Jan 26 00:00:00 1970 | 5 | 5 | foo
435 | 5 | 00435 | Thu Feb 05 00:00:00 1970 PST | Thu Feb 05 00:00:00 1970 | 5 | 5 | foo
445 | 5 | 00445 | Sun Feb 15 00:00:00 1970 PST | Sun Feb 15 00:00:00 1970 | 5 | 5 | foo
455 | 5 | 00455 | Wed Feb 25 00:00:00 1970 PST | Wed Feb 25 00:00:00 1970 | 5 | 5 | foo
465 | 5 | 00465 | Sat Mar 07 00:00:00 1970 PST | Sat Mar 07 00:00:00 1970 | 5 | 5 | foo
475 | 5 | 00475 | Tue Mar 17 00:00:00 1970 PST | Tue Mar 17 00:00:00 1970 | 5 | 5 | foo
485 | 5 | 00485 | Fri Mar 27 00:00:00 1970 PST | Fri Mar 27 00:00:00 1970 | 5 | 5 | foo
495 | 5 | 00495 | Mon Apr 06 00:00:00 1970 PST | Mon Apr 06 00:00:00 1970 | 5 | 5 | foo
505 | 5 | 00505 | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5 | 5 | foo
515 | 5 | 00515 | Fri Jan 16 00:00:00 1970 PST | Fri Jan 16 00:00:00 1970 | 5 | 5 | foo
525 | 5 | 00525 | Mon Jan 26 00:00:00 1970 PST | Mon Jan 26 00:00:00 1970 | 5 | 5 | foo
535 | 5 | 00535 | Thu Feb 05 00:00:00 1970 PST | Thu Feb 05 00:00:00 1970 | 5 | 5 | foo
545 | 5 | 00545 | Sun Feb 15 00:00:00 1970 PST | Sun Feb 15 00:00:00 1970 | 5 | 5 | foo
555 | 5 | 00555 | Wed Feb 25 00:00:00 1970 PST | Wed Feb 25 00:00:00 1970 | 5 | 5 | foo
565 | 5 | 00565 | Sat Mar 07 00:00:00 1970 PST | Sat Mar 07 00:00:00 1970 | 5 | 5 | foo
575 | 5 | 00575 | Tue Mar 17 00:00:00 1970 PST | Tue Mar 17 00:00:00 1970 | 5 | 5 | foo
585 | 5 | 00585 | Fri Mar 27 00:00:00 1970 PST | Fri Mar 27 00:00:00 1970 | 5 | 5 | foo
595 | 5 | 00595 | Mon Apr 06 00:00:00 1970 PST | Mon Apr 06 00:00:00 1970 | 5 | 5 | foo
605 | 5 | 00605 | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5 | 5 | foo
615 | 5 | 00615 | Fri Jan 16 00:00:00 1970 PST | Fri Jan 16 00:00:00 1970 | 5 | 5 | foo
625 | 5 | 00625 | Mon Jan 26 00:00:00 1970 PST | Mon Jan 26 00:00:00 1970 | 5 | 5 | foo
635 | 5 | 00635 | Thu Feb 05 00:00:00 1970 PST | Thu Feb 05 00:00:00 1970 | 5 | 5 | foo
645 | 5 | 00645 | Sun Feb 15 00:00:00 1970 PST | Sun Feb 15 00:00:00 1970 | 5 | 5 | foo
655 | 5 | 00655 | Wed Feb 25 00:00:00 1970 PST | Wed Feb 25 00:00:00 1970 | 5 | 5 | foo
665 | 5 | 00665 | Sat Mar 07 00:00:00 1970 PST | Sat Mar 07 00:00:00 1970 | 5 | 5 | foo
675 | 5 | 00675 | Tue Mar 17 00:00:00 1970 PST | Tue Mar 17 00:00:00 1970 | 5 | 5 | foo
685 | 5 | 00685 | Fri Mar 27 00:00:00 1970 PST | Fri Mar 27 00:00:00 1970 | 5 | 5 | foo
695 | 5 | 00695 | Mon Apr 06 00:00:00 1970 PST | Mon Apr 06 00:00:00 1970 | 5 | 5 | foo
705 | 5 | 00705 | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5 | 5 | foo
715 | 5 | 00715 | Fri Jan 16 00:00:00 1970 PST | Fri Jan 16 00:00:00 1970 | 5 | 5 | foo
725 | 5 | 00725 | Mon Jan 26 00:00:00 1970 PST | Mon Jan 26 00:00:00 1970 | 5 | 5 | foo
735 | 5 | 00735 | Thu Feb 05 00:00:00 1970 PST | Thu Feb 05 00:00:00 1970 | 5 | 5 | foo
745 | 5 | 00745 | Sun Feb 15 00:00:00 1970 PST | Sun Feb 15 00:00:00 1970 | 5 | 5 | foo
755 | 5 | 00755 | Wed Feb 25 00:00:00 1970 PST | Wed Feb 25 00:00:00 1970 | 5 | 5 | foo
765 | 5 | 00765 | Sat Mar 07 00:00:00 1970 PST | Sat Mar 07 00:00:00 1970 | 5 | 5 | foo
775 | 5 | 00775 | Tue Mar 17 00:00:00 1970 PST | Tue Mar 17 00:00:00 1970 | 5 | 5 | foo
785 | 5 | 00785 | Fri Mar 27 00:00:00 1970 PST | Fri Mar 27 00:00:00 1970 | 5 | 5 | foo
795 | 5 | 00795 | Mon Apr 06 00:00:00 1970 PST | Mon Apr 06 00:00:00 1970 | 5 | 5 | foo
805 | 5 | 00805 | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5 | 5 | foo
815 | 5 | 00815 | Fri Jan 16 00:00:00 1970 PST | Fri Jan 16 00:00:00 1970 | 5 | 5 | foo
825 | 5 | 00825 | Mon Jan 26 00:00:00 1970 PST | Mon Jan 26 00:00:00 1970 | 5 | 5 | foo
835 | 5 | 00835 | Thu Feb 05 00:00:00 1970 PST | Thu Feb 05 00:00:00 1970 | 5 | 5 | foo
845 | 5 | 00845 | Sun Feb 15 00:00:00 1970 PST | Sun Feb 15 00:00:00 1970 | 5 | 5 | foo
855 | 5 | 00855 | Wed Feb 25 00:00:00 1970 PST | Wed Feb 25 00:00:00 1970 | 5 | 5 | foo
865 | 5 | 00865 | Sat Mar 07 00:00:00 1970 PST | Sat Mar 07 00:00:00 1970 | 5 | 5 | foo
875 | 5 | 00875 | Tue Mar 17 00:00:00 1970 PST | Tue Mar 17 00:00:00 1970 | 5 | 5 | foo
885 | 5 | 00885 | Fri Mar 27 00:00:00 1970 PST | Fri Mar 27 00:00:00 1970 | 5 | 5 | foo
895 | 5 | 00895 | Mon Apr 06 00:00:00 1970 PST | Mon Apr 06 00:00:00 1970 | 5 | 5 | foo
905 | 5 | 00905 | Tue Jan 06 00:00:00 1970 PST | Tue Jan 06 00:00:00 1970 | 5 | 5 | foo
915 | 5 | 00915 | Fri Jan 16 00:00:00 1970 PST | Fri Jan 16 00:00:00 1970 | 5 | 5 | foo
925 | 5 | 00925 | Mon Jan 26 00:00:00 1970 PST | Mon Jan 26 00:00:00 1970 | 5 | 5 | foo
935 | 5 | 00935 | Thu Feb 05 00:00:00 1970 PST | Thu Feb 05 00:00:00 1970 | 5 | 5 | foo
945 | 5 | 00945 | Sun Feb 15 00:00:00 1970 PST | Sun Feb 15 00:00:00 1970 | 5 | 5 | foo
955 | 5 | 00955 | Wed Feb 25 00:00:00 1970 PST | Wed Feb 25 00:00:00 1970 | 5 | 5 | foo
965 | 5 | 00965 | Sat Mar 07 00:00:00 1970 PST | Sat Mar 07 00:00:00 1970 | 5 | 5 | foo
975 | 5 | 00975 | Tue Mar 17 00:00:00 1970 PST | Tue Mar 17 00:00:00 1970 | 5 | 5 | foo
985 | 5 | 00985 | Fri Mar 27 00:00:00 1970 PST | Fri Mar 27 00:00:00 1970 | 5 | 5 | foo
995 | 5 | 00995 | Mon Apr 06 00:00:00 1970 PST | Mon Apr 06 00:00:00 1970 | 5 | 5 | foo
1005 | 105 | 0000500005 | | | | |
1015 | 105 | 0001500015 | | | | |
1105 | 205 | eee | | | | |
(103 rows)
EXPLAIN (verbose, costs off)
DELETE FROM ft2 USING ft1 WHERE ft1.c1 = ft2.c2 AND ft1.c1 % 10 = 2;
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------
Delete on public.ft2
Remote SQL: DELETE FROM "S 1"."T 1" WHERE ctid = $1
-> Hash Join
Output: ft2.ctid, ft1.*
Hash Cond: (ft2.c2 = ft1.c1)
-> Foreign Scan on public.ft2
Output: ft2.ctid, ft2.c2
Remote SQL: SELECT NULL, c2, NULL, NULL, NULL, NULL, NULL, NULL, ctid FROM "S 1"."T 1" FOR UPDATE
-> Hash
Output: ft1.*, ft1.c1
-> Foreign Scan on public.ft1
Output: ft1.*, ft1.c1
Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE ((("C 1" % 10) = 2))
(13 rows)
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;
c1 | c2 | c3 | c4
------+-----+--------------------+------------------------------
1 | 1 | 00001 | Fri Jan 02 00:00:00 1970 PST
3 | 303 | 00003_update3 | Sun Jan 04 00:00:00 1970 PST
4 | 4 | 00004 | Mon Jan 05 00:00:00 1970 PST
6 | 6 | 00006 | Wed Jan 07 00:00:00 1970 PST
7 | 407 | 00007_update7 | Thu Jan 08 00:00:00 1970 PST
8 | 8 | 00008 | Fri Jan 09 00:00:00 1970 PST
9 | 509 | 00009_update9 | Sat Jan 10 00:00:00 1970 PST
10 | 0 | 00010 | Sun Jan 11 00:00:00 1970 PST
11 | 1 | 00011 | Mon Jan 12 00:00:00 1970 PST
13 | 303 | 00013_update3 | Wed Jan 14 00:00:00 1970 PST
14 | 4 | 00014 | Thu Jan 15 00:00:00 1970 PST
16 | 6 | 00016 | Sat Jan 17 00:00:00 1970 PST
17 | 407 | 00017_update7 | Sun Jan 18 00:00:00 1970 PST
18 | 8 | 00018 | Mon Jan 19 00:00:00 1970 PST
19 | 509 | 00019_update9 | Tue Jan 20 00:00:00 1970 PST
20 | 0 | 00020 | Wed Jan 21 00:00:00 1970 PST
21 | 1 | 00021 | Thu Jan 22 00:00:00 1970 PST
23 | 303 | 00023_update3 | Sat Jan 24 00:00:00 1970 PST
24 | 4 | 00024 | Sun Jan 25 00:00:00 1970 PST
26 | 6 | 00026 | Tue Jan 27 00:00:00 1970 PST
27 | 407 | 00027_update7 | Wed Jan 28 00:00:00 1970 PST
28 | 8 | 00028 | Thu Jan 29 00:00:00 1970 PST
29 | 509 | 00029_update9 | Fri Jan 30 00:00:00 1970 PST
30 | 0 | 00030 | Sat Jan 31 00:00:00 1970 PST
31 | 1 | 00031 | Sun Feb 01 00:00:00 1970 PST
33 | 303 | 00033_update3 | Tue Feb 03 00:00:00 1970 PST
34 | 4 | 00034 | Wed Feb 04 00:00:00 1970 PST
36 | 6 | 00036 | Fri Feb 06 00:00:00 1970 PST
37 | 407 | 00037_update7 | Sat Feb 07 00:00:00 1970 PST
38 | 8 | 00038 | Sun Feb 08 00:00:00 1970 PST
39 | 509 | 00039_update9 | Mon Feb 09 00:00:00 1970 PST
40 | 0 | 00040 | Tue Feb 10 00:00:00 1970 PST
41 | 1 | 00041 | Wed Feb 11 00:00:00 1970 PST
43 | 303 | 00043_update3 | Fri Feb 13 00:00:00 1970 PST
44 | 4 | 00044 | Sat Feb 14 00:00:00 1970 PST
46 | 6 | 00046 | Mon Feb 16 00:00:00 1970 PST
47 | 407 | 00047_update7 | Tue Feb 17 00:00:00 1970 PST
48 | 8 | 00048 | Wed Feb 18 00:00:00 1970 PST
49 | 509 | 00049_update9 | Thu Feb 19 00:00:00 1970 PST
50 | 0 | 00050 | Fri Feb 20 00:00:00 1970 PST
51 | 1 | 00051 | Sat Feb 21 00:00:00 1970 PST
53 | 303 | 00053_update3 | Mon Feb 23 00:00:00 1970 PST
54 | 4 | 00054 | Tue Feb 24 00:00:00 1970 PST
56 | 6 | 00056 | Thu Feb 26 00:00:00 1970 PST
57 | 407 | 00057_update7 | Fri Feb 27 00:00:00 1970 PST
58 | 8 | 00058 | Sat Feb 28 00:00:00 1970 PST
59 | 509 | 00059_update9 | Sun Mar 01 00:00:00 1970 PST
60 | 0 | 00060 | Mon Mar 02 00:00:00 1970 PST
61 | 1 | 00061 | Tue Mar 03 00:00:00 1970 PST
63 | 303 | 00063_update3 | Thu Mar 05 00:00:00 1970 PST
64 | 4 | 00064 | Fri Mar 06 00:00:00 1970 PST
66 | 6 | 00066 | Sun Mar 08 00:00:00 1970 PST
67 | 407 | 00067_update7 | Mon Mar 09 00:00:00 1970 PST
68 | 8 | 00068 | Tue Mar 10 00:00:00 1970 PST
69 | 509 | 00069_update9 | Wed Mar 11 00:00:00 1970 PST
70 | 0 | 00070 | Thu Mar 12 00:00:00 1970 PST
71 | 1 | 00071 | Fri Mar 13 00:00:00 1970 PST
73 | 303 | 00073_update3 | Sun Mar 15 00:00:00 1970 PST
74 | 4 | 00074 | Mon Mar 16 00:00:00 1970 PST
76 | 6 | 00076 | Wed Mar 18 00:00:00 1970 PST
77 | 407 | 00077_update7 | Thu Mar 19 00:00:00 1970 PST
78 | 8 | 00078 | Fri Mar 20 00:00:00 1970 PST
79 | 509 | 00079_update9 | Sat Mar 21 00:00:00 1970 PST
80 | 0 | 00080 | Sun Mar 22 00:00:00 1970 PST
81 | 1 | 00081 | Mon Mar 23 00:00:00 1970 PST
83 | 303 | 00083_update3 | Wed Mar 25 00:00:00 1970 PST
84 | 4 | 00084 | Thu Mar 26 00:00:00 1970 PST
86 | 6 | 00086 | Sat Mar 28 00:00:00 1970 PST
87 | 407 | 00087_update7 | Sun Mar 29 00:00:00 1970 PST
88 | 8 | 00088 | Mon Mar 30 00:00:00 1970 PST
89 | 509 | 00089_update9 | Tue Mar 31 00:00:00 1970 PST
90 | 0 | 00090 | Wed Apr 01 00:00:00 1970 PST
91 | 1 | 00091 | Thu Apr 02 00:00:00 1970 PST
93 | 303 | 00093_update3 | Sat Apr 04 00:00:00 1970 PST
94 | 4 | 00094 | Sun Apr 05 00:00:00 1970 PST
96 | 6 | 00096 | Tue Apr 07 00:00:00 1970 PST
97 | 407 | 00097_update7 | Wed Apr 08 00:00:00 1970 PST
98 | 8 | 00098 | Thu Apr 09 00:00:00 1970 PST
99 | 509 | 00099_update9 | Fri Apr 10 00:00:00 1970 PST
100 | 0 | 00100 | Thu Jan 01 00:00:00 1970 PST
101 | 1 | 00101 | Fri Jan 02 00:00:00 1970 PST
103 | 303 | 00103_update3 | Sun Jan 04 00:00:00 1970 PST
104 | 4 | 00104 | Mon Jan 05 00:00:00 1970 PST
106 | 6 | 00106 | Wed Jan 07 00:00:00 1970 PST
107 | 407 | 00107_update7 | Thu Jan 08 00:00:00 1970 PST
108 | 8 | 00108 | Fri Jan 09 00:00:00 1970 PST
109 | 509 | 00109_update9 | Sat Jan 10 00:00:00 1970 PST
110 | 0 | 00110 | Sun Jan 11 00:00:00 1970 PST
111 | 1 | 00111 | Mon Jan 12 00:00:00 1970 PST
113 | 303 | 00113_update3 | Wed Jan 14 00:00:00 1970 PST
114 | 4 | 00114 | Thu Jan 15 00:00:00 1970 PST
116 | 6 | 00116 | Sat Jan 17 00:00:00 1970 PST
117 | 407 | 00117_update7 | Sun Jan 18 00:00:00 1970 PST
118 | 8 | 00118 | Mon Jan 19 00:00:00 1970 PST
119 | 509 | 00119_update9 | Tue Jan 20 00:00:00 1970 PST
120 | 0 | 00120 | Wed Jan 21 00:00:00 1970 PST
121 | 1 | 00121 | Thu Jan 22 00:00:00 1970 PST
123 | 303 | 00123_update3 | Sat Jan 24 00:00:00 1970 PST
124 | 4 | 00124 | Sun Jan 25 00:00:00 1970 PST
126 | 6 | 00126 | Tue Jan 27 00:00:00 1970 PST
127 | 407 | 00127_update7 | Wed Jan 28 00:00:00 1970 PST
128 | 8 | 00128 | Thu Jan 29 00:00:00 1970 PST
129 | 509 | 00129_update9 | Fri Jan 30 00:00:00 1970 PST
130 | 0 | 00130 | Sat Jan 31 00:00:00 1970 PST
131 | 1 | 00131 | Sun Feb 01 00:00:00 1970 PST
133 | 303 | 00133_update3 | Tue Feb 03 00:00:00 1970 PST
134 | 4 | 00134 | Wed Feb 04 00:00:00 1970 PST
136 | 6 | 00136 | Fri Feb 06 00:00:00 1970 PST
137 | 407 | 00137_update7 | Sat Feb 07 00:00:00 1970 PST
138 | 8 | 00138 | Sun Feb 08 00:00:00 1970 PST
139 | 509 | 00139_update9 | Mon Feb 09 00:00:00 1970 PST
140 | 0 | 00140 | Tue Feb 10 00:00:00 1970 PST
141 | 1 | 00141 | Wed Feb 11 00:00:00 1970 PST
143 | 303 | 00143_update3 | Fri Feb 13 00:00:00 1970 PST
144 | 4 | 00144 | Sat Feb 14 00:00:00 1970 PST
146 | 6 | 00146 | Mon Feb 16 00:00:00 1970 PST
147 | 407 | 00147_update7 | Tue Feb 17 00:00:00 1970 PST
148 | 8 | 00148 | Wed Feb 18 00:00:00 1970 PST
149 | 509 | 00149_update9 | Thu Feb 19 00:00:00 1970 PST
150 | 0 | 00150 | Fri Feb 20 00:00:00 1970 PST
151 | 1 | 00151 | Sat Feb 21 00:00:00 1970 PST
153 | 303 | 00153_update3 | Mon Feb 23 00:00:00 1970 PST
154 | 4 | 00154 | Tue Feb 24 00:00:00 1970 PST
156 | 6 | 00156 | Thu Feb 26 00:00:00 1970 PST
157 | 407 | 00157_update7 | Fri Feb 27 00:00:00 1970 PST
158 | 8 | 00158 | Sat Feb 28 00:00:00 1970 PST
159 | 509 | 00159_update9 | Sun Mar 01 00:00:00 1970 PST
160 | 0 | 00160 | Mon Mar 02 00:00:00 1970 PST
161 | 1 | 00161 | Tue Mar 03 00:00:00 1970 PST
163 | 303 | 00163_update3 | Thu Mar 05 00:00:00 1970 PST
164 | 4 | 00164 | Fri Mar 06 00:00:00 1970 PST
166 | 6 | 00166 | Sun Mar 08 00:00:00 1970 PST
167 | 407 | 00167_update7 | Mon Mar 09 00:00:00 1970 PST
168 | 8 | 00168 | Tue Mar 10 00:00:00 1970 PST
169 | 509 | 00169_update9 | Wed Mar 11 00:00:00 1970 PST
170 | 0 | 00170 | Thu Mar 12 00:00:00 1970 PST
171 | 1 | 00171 | Fri Mar 13 00:00:00 1970 PST
173 | 303 | 00173_update3 | Sun Mar 15 00:00:00 1970 PST
174 | 4 | 00174 | Mon Mar 16 00:00:00 1970 PST
176 | 6 | 00176 | Wed Mar 18 00:00:00 1970 PST
177 | 407 | 00177_update7 | Thu Mar 19 00:00:00 1970 PST
178 | 8 | 00178 | Fri Mar 20 00:00:00 1970 PST
179 | 509 | 00179_update9 | Sat Mar 21 00:00:00 1970 PST
180 | 0 | 00180 | Sun Mar 22 00:00:00 1970 PST
181 | 1 | 00181 | Mon Mar 23 00:00:00 1970 PST
183 | 303 | 00183_update3 | Wed Mar 25 00:00:00 1970 PST
184 | 4 | 00184 | Thu Mar 26 00:00:00 1970 PST
186 | 6 | 00186 | Sat Mar 28 00:00:00 1970 PST
187 | 407 | 00187_update7 | Sun Mar 29 00:00:00 1970 PST
188 | 8 | 00188 | Mon Mar 30 00:00:00 1970 PST
189 | 509 | 00189_update9 | Tue Mar 31 00:00:00 1970 PST
190 | 0 | 00190 | Wed Apr 01 00:00:00 1970 PST
191 | 1 | 00191 | Thu Apr 02 00:00:00 1970 PST
193 | 303 | 00193_update3 | Sat Apr 04 00:00:00 1970 PST
194 | 4 | 00194 | Sun Apr 05 00:00:00 1970 PST
196 | 6 | 00196 | Tue Apr 07 00:00:00 1970 PST
197 | 407 | 00197_update7 | Wed Apr 08 00:00:00 1970 PST
198 | 8 | 00198 | Thu Apr 09 00:00:00 1970 PST
199 | 509 | 00199_update9 | Fri Apr 10 00:00:00 1970 PST
200 | 0 | 00200 | Thu Jan 01 00:00:00 1970 PST
201 | 1 | 00201 | Fri Jan 02 00:00:00 1970 PST
203 | 303 | 00203_update3 | Sun Jan 04 00:00:00 1970 PST
204 | 4 | 00204 | Mon Jan 05 00:00:00 1970 PST
206 | 6 | 00206 | Wed Jan 07 00:00:00 1970 PST
207 | 407 | 00207_update7 | Thu Jan 08 00:00:00 1970 PST
208 | 8 | 00208 | Fri Jan 09 00:00:00 1970 PST
209 | 509 | 00209_update9 | Sat Jan 10 00:00:00 1970 PST
210 | 0 | 00210 | Sun Jan 11 00:00:00 1970 PST
211 | 1 | 00211 | Mon Jan 12 00:00:00 1970 PST
213 | 303 | 00213_update3 | Wed Jan 14 00:00:00 1970 PST
214 | 4 | 00214 | Thu Jan 15 00:00:00 1970 PST
216 | 6 | 00216 | Sat Jan 17 00:00:00 1970 PST
217 | 407 | 00217_update7 | Sun Jan 18 00:00:00 1970 PST
218 | 8 | 00218 | Mon Jan 19 00:00:00 1970 PST
219 | 509 | 00219_update9 | Tue Jan 20 00:00:00 1970 PST
220 | 0 | 00220 | Wed Jan 21 00:00:00 1970 PST
221 | 1 | 00221 | Thu Jan 22 00:00:00 1970 PST
223 | 303 | 00223_update3 | Sat Jan 24 00:00:00 1970 PST
224 | 4 | 00224 | Sun Jan 25 00:00:00 1970 PST
226 | 6 | 00226 | Tue Jan 27 00:00:00 1970 PST
227 | 407 | 00227_update7 | Wed Jan 28 00:00:00 1970 PST
228 | 8 | 00228 | Thu Jan 29 00:00:00 1970 PST
229 | 509 | 00229_update9 | Fri Jan 30 00:00:00 1970 PST
230 | 0 | 00230 | Sat Jan 31 00:00:00 1970 PST
231 | 1 | 00231 | Sun Feb 01 00:00:00 1970 PST
233 | 303 | 00233_update3 | Tue Feb 03 00:00:00 1970 PST
234 | 4 | 00234 | Wed Feb 04 00:00:00 1970 PST
236 | 6 | 00236 | Fri Feb 06 00:00:00 1970 PST
237 | 407 | 00237_update7 | Sat Feb 07 00:00:00 1970 PST
238 | 8 | 00238 | Sun Feb 08 00:00:00 1970 PST
239 | 509 | 00239_update9 | Mon Feb 09 00:00:00 1970 PST
240 | 0 | 00240 | Tue Feb 10 00:00:00 1970 PST
241 | 1 | 00241 | Wed Feb 11 00:00:00 1970 PST
243 | 303 | 00243_update3 | Fri Feb 13 00:00:00 1970 PST
244 | 4 | 00244 | Sat Feb 14 00:00:00 1970 PST
246 | 6 | 00246 | Mon Feb 16 00:00:00 1970 PST
247 | 407 | 00247_update7 | Tue Feb 17 00:00:00 1970 PST
248 | 8 | 00248 | Wed Feb 18 00:00:00 1970 PST
249 | 509 | 00249_update9 | Thu Feb 19 00:00:00 1970 PST
250 | 0 | 00250 | Fri Feb 20 00:00:00 1970 PST
251 | 1 | 00251 | Sat Feb 21 00:00:00 1970 PST
253 | 303 | 00253_update3 | Mon Feb 23 00:00:00 1970 PST
254 | 4 | 00254 | Tue Feb 24 00:00:00 1970 PST
256 | 6 | 00256 | Thu Feb 26 00:00:00 1970 PST
257 | 407 | 00257_update7 | Fri Feb 27 00:00:00 1970 PST
258 | 8 | 00258 | Sat Feb 28 00:00:00 1970 PST
259 | 509 | 00259_update9 | Sun Mar 01 00:00:00 1970 PST
260 | 0 | 00260 | Mon Mar 02 00:00:00 1970 PST
261 | 1 | 00261 | Tue Mar 03 00:00:00 1970 PST
263 | 303 | 00263_update3 | Thu Mar 05 00:00:00 1970 PST
264 | 4 | 00264 | Fri Mar 06 00:00:00 1970 PST
266 | 6 | 00266 | Sun Mar 08 00:00:00 1970 PST
267 | 407 | 00267_update7 | Mon Mar 09 00:00:00 1970 PST
268 | 8 | 00268 | Tue Mar 10 00:00:00 1970 PST
269 | 509 | 00269_update9 | Wed Mar 11 00:00:00 1970 PST
270 | 0 | 00270 | Thu Mar 12 00:00:00 1970 PST
271 | 1 | 00271 | Fri Mar 13 00:00:00 1970 PST
273 | 303 | 00273_update3 | Sun Mar 15 00:00:00 1970 PST
274 | 4 | 00274 | Mon Mar 16 00:00:00 1970 PST
276 | 6 | 00276 | Wed Mar 18 00:00:00 1970 PST
277 | 407 | 00277_update7 | Thu Mar 19 00:00:00 1970 PST
278 | 8 | 00278 | Fri Mar 20 00:00:00 1970 PST
279 | 509 | 00279_update9 | Sat Mar 21 00:00:00 1970 PST
280 | 0 | 00280 | Sun Mar 22 00:00:00 1970 PST
281 | 1 | 00281 | Mon Mar 23 00:00:00 1970 PST
283 | 303 | 00283_update3 | Wed Mar 25 00:00:00 1970 PST
284 | 4 | 00284 | Thu Mar 26 00:00:00 1970 PST
286 | 6 | 00286 | Sat Mar 28 00:00:00 1970 PST
287 | 407 | 00287_update7 | Sun Mar 29 00:00:00 1970 PST
288 | 8 | 00288 | Mon Mar 30 00:00:00 1970 PST
289 | 509 | 00289_update9 | Tue Mar 31 00:00:00 1970 PST
290 | 0 | 00290 | Wed Apr 01 00:00:00 1970 PST
291 | 1 | 00291 | Thu Apr 02 00:00:00 1970 PST
293 | 303 | 00293_update3 | Sat Apr 04 00:00:00 1970 PST
294 | 4 | 00294 | Sun Apr 05 00:00:00 1970 PST
296 | 6 | 00296 | Tue Apr 07 00:00:00 1970 PST
297 | 407 | 00297_update7 | Wed Apr 08 00:00:00 1970 PST
298 | 8 | 00298 | Thu Apr 09 00:00:00 1970 PST
299 | 509 | 00299_update9 | Fri Apr 10 00:00:00 1970 PST
300 | 0 | 00300 | Thu Jan 01 00:00:00 1970 PST
301 | 1 | 00301 | Fri Jan 02 00:00:00 1970 PST
303 | 303 | 00303_update3 | Sun Jan 04 00:00:00 1970 PST
304 | 4 | 00304 | Mon Jan 05 00:00:00 1970 PST
306 | 6 | 00306 | Wed Jan 07 00:00:00 1970 PST
307 | 407 | 00307_update7 | Thu Jan 08 00:00:00 1970 PST
308 | 8 | 00308 | Fri Jan 09 00:00:00 1970 PST
309 | 509 | 00309_update9 | Sat Jan 10 00:00:00 1970 PST
310 | 0 | 00310 | Sun Jan 11 00:00:00 1970 PST
311 | 1 | 00311 | Mon Jan 12 00:00:00 1970 PST
313 | 303 | 00313_update3 | Wed Jan 14 00:00:00 1970 PST
314 | 4 | 00314 | Thu Jan 15 00:00:00 1970 PST
316 | 6 | 00316 | Sat Jan 17 00:00:00 1970 PST
317 | 407 | 00317_update7 | Sun Jan 18 00:00:00 1970 PST
318 | 8 | 00318 | Mon Jan 19 00:00:00 1970 PST
319 | 509 | 00319_update9 | Tue Jan 20 00:00:00 1970 PST
320 | 0 | 00320 | Wed Jan 21 00:00:00 1970 PST
321 | 1 | 00321 | Thu Jan 22 00:00:00 1970 PST
323 | 303 | 00323_update3 | Sat Jan 24 00:00:00 1970 PST
324 | 4 | 00324 | Sun Jan 25 00:00:00 1970 PST
326 | 6 | 00326 | Tue Jan 27 00:00:00 1970 PST
327 | 407 | 00327_update7 | Wed Jan 28 00:00:00 1970 PST
328 | 8 | 00328 | Thu Jan 29 00:00:00 1970 PST
329 | 509 | 00329_update9 | Fri Jan 30 00:00:00 1970 PST
330 | 0 | 00330 | Sat Jan 31 00:00:00 1970 PST
331 | 1 | 00331 | Sun Feb 01 00:00:00 1970 PST
333 | 303 | 00333_update3 | Tue Feb 03 00:00:00 1970 PST
334 | 4 | 00334 | Wed Feb 04 00:00:00 1970 PST
336 | 6 | 00336 | Fri Feb 06 00:00:00 1970 PST
337 | 407 | 00337_update7 | Sat Feb 07 00:00:00 1970 PST
338 | 8 | 00338 | Sun Feb 08 00:00:00 1970 PST
339 | 509 | 00339_update9 | Mon Feb 09 00:00:00 1970 PST
340 | 0 | 00340 | Tue Feb 10 00:00:00 1970 PST
341 | 1 | 00341 | Wed Feb 11 00:00:00 1970 PST
343 | 303 | 00343_update3 | Fri Feb 13 00:00:00 1970 PST
344 | 4 | 00344 | Sat Feb 14 00:00:00 1970 PST
346 | 6 | 00346 | Mon Feb 16 00:00:00 1970 PST
347 | 407 | 00347_update7 | Tue Feb 17 00:00:00 1970 PST
348 | 8 | 00348 | Wed Feb 18 00:00:00 1970 PST
349 | 509 | 00349_update9 | Thu Feb 19 00:00:00 1970 PST
350 | 0 | 00350 | Fri Feb 20 00:00:00 1970 PST
351 | 1 | 00351 | Sat Feb 21 00:00:00 1970 PST
353 | 303 | 00353_update3 | Mon Feb 23 00:00:00 1970 PST
354 | 4 | 00354 | Tue Feb 24 00:00:00 1970 PST
356 | 6 | 00356 | Thu Feb 26 00:00:00 1970 PST
357 | 407 | 00357_update7 | Fri Feb 27 00:00:00 1970 PST
358 | 8 | 00358 | Sat Feb 28 00:00:00 1970 PST
359 | 509 | 00359_update9 | Sun Mar 01 00:00:00 1970 PST
360 | 0 | 00360 | Mon Mar 02 00:00:00 1970 PST
361 | 1 | 00361 | Tue Mar 03 00:00:00 1970 PST
363 | 303 | 00363_update3 | Thu Mar 05 00:00:00 1970 PST
364 | 4 | 00364 | Fri Mar 06 00:00:00 1970 PST
366 | 6 | 00366 | Sun Mar 08 00:00:00 1970 PST
367 | 407 | 00367_update7 | Mon Mar 09 00:00:00 1970 PST
368 | 8 | 00368 | Tue Mar 10 00:00:00 1970 PST
369 | 509 | 00369_update9 | Wed Mar 11 00:00:00 1970 PST
370 | 0 | 00370 | Thu Mar 12 00:00:00 1970 PST
371 | 1 | 00371 | Fri Mar 13 00:00:00 1970 PST
373 | 303 | 00373_update3 | Sun Mar 15 00:00:00 1970 PST
374 | 4 | 00374 | Mon Mar 16 00:00:00 1970 PST
376 | 6 | 00376 | Wed Mar 18 00:00:00 1970 PST
377 | 407 | 00377_update7 | Thu Mar 19 00:00:00 1970 PST
378 | 8 | 00378 | Fri Mar 20 00:00:00 1970 PST
379 | 509 | 00379_update9 | Sat Mar 21 00:00:00 1970 PST
380 | 0 | 00380 | Sun Mar 22 00:00:00 1970 PST
381 | 1 | 00381 | Mon Mar 23 00:00:00 1970 PST
383 | 303 | 00383_update3 | Wed Mar 25 00:00:00 1970 PST
384 | 4 | 00384 | Thu Mar 26 00:00:00 1970 PST
386 | 6 | 00386 | Sat Mar 28 00:00:00 1970 PST
387 | 407 | 00387_update7 | Sun Mar 29 00:00:00 1970 PST
388 | 8 | 00388 | Mon Mar 30 00:00:00 1970 PST
389 | 509 | 00389_update9 | Tue Mar 31 00:00:00 1970 PST
390 | 0 | 00390 | Wed Apr 01 00:00:00 1970 PST
391 | 1 | 00391 | Thu Apr 02 00:00:00 1970 PST
393 | 303 | 00393_update3 | Sat Apr 04 00:00:00 1970 PST
394 | 4 | 00394 | Sun Apr 05 00:00:00 1970 PST
396 | 6 | 00396 | Tue Apr 07 00:00:00 1970 PST
397 | 407 | 00397_update7 | Wed Apr 08 00:00:00 1970 PST
398 | 8 | 00398 | Thu Apr 09 00:00:00 1970 PST
399 | 509 | 00399_update9 | Fri Apr 10 00:00:00 1970 PST
400 | 0 | 00400 | Thu Jan 01 00:00:00 1970 PST
401 | 1 | 00401 | Fri Jan 02 00:00:00 1970 PST
403 | 303 | 00403_update3 | Sun Jan 04 00:00:00 1970 PST
404 | 4 | 00404 | Mon Jan 05 00:00:00 1970 PST
406 | 6 | 00406 | Wed Jan 07 00:00:00 1970 PST
407 | 407 | 00407_update7 | Thu Jan 08 00:00:00 1970 PST
408 | 8 | 00408 | Fri Jan 09 00:00:00 1970 PST
409 | 509 | 00409_update9 | Sat Jan 10 00:00:00 1970 PST
410 | 0 | 00410 | Sun Jan 11 00:00:00 1970 PST
411 | 1 | 00411 | Mon Jan 12 00:00:00 1970 PST
413 | 303 | 00413_update3 | Wed Jan 14 00:00:00 1970 PST
414 | 4 | 00414 | Thu Jan 15 00:00:00 1970 PST
416 | 6 | 00416 | Sat Jan 17 00:00:00 1970 PST
417 | 407 | 00417_update7 | Sun Jan 18 00:00:00 1970 PST
418 | 8 | 00418 | Mon Jan 19 00:00:00 1970 PST
419 | 509 | 00419_update9 | Tue Jan 20 00:00:00 1970 PST
420 | 0 | 00420 | Wed Jan 21 00:00:00 1970 PST
421 | 1 | 00421 | Thu Jan 22 00:00:00 1970 PST
423 | 303 | 00423_update3 | Sat Jan 24 00:00:00 1970 PST
424 | 4 | 00424 | Sun Jan 25 00:00:00 1970 PST
426 | 6 | 00426 | Tue Jan 27 00:00:00 1970 PST
427 | 407 | 00427_update7 | Wed Jan 28 00:00:00 1970 PST
428 | 8 | 00428 | Thu Jan 29 00:00:00 1970 PST
429 | 509 | 00429_update9 | Fri Jan 30 00:00:00 1970 PST
430 | 0 | 00430 | Sat Jan 31 00:00:00 1970 PST
431 | 1 | 00431 | Sun Feb 01 00:00:00 1970 PST
433 | 303 | 00433_update3 | Tue Feb 03 00:00:00 1970 PST
434 | 4 | 00434 | Wed Feb 04 00:00:00 1970 PST
436 | 6 | 00436 | Fri Feb 06 00:00:00 1970 PST
437 | 407 | 00437_update7 | Sat Feb 07 00:00:00 1970 PST
438 | 8 | 00438 | Sun Feb 08 00:00:00 1970 PST
439 | 509 | 00439_update9 | Mon Feb 09 00:00:00 1970 PST
440 | 0 | 00440 | Tue Feb 10 00:00:00 1970 PST
441 | 1 | 00441 | Wed Feb 11 00:00:00 1970 PST
443 | 303 | 00443_update3 | Fri Feb 13 00:00:00 1970 PST
444 | 4 | 00444 | Sat Feb 14 00:00:00 1970 PST
446 | 6 | 00446 | Mon Feb 16 00:00:00 1970 PST
447 | 407 | 00447_update7 | Tue Feb 17 00:00:00 1970 PST
448 | 8 | 00448 | Wed Feb 18 00:00:00 1970 PST
449 | 509 | 00449_update9 | Thu Feb 19 00:00:00 1970 PST
450 | 0 | 00450 | Fri Feb 20 00:00:00 1970 PST
451 | 1 | 00451 | Sat Feb 21 00:00:00 1970 PST
453 | 303 | 00453_update3 | Mon Feb 23 00:00:00 1970 PST
454 | 4 | 00454 | Tue Feb 24 00:00:00 1970 PST
456 | 6 | 00456 | Thu Feb 26 00:00:00 1970 PST
457 | 407 | 00457_update7 | Fri Feb 27 00:00:00 1970 PST
458 | 8 | 00458 | Sat Feb 28 00:00:00 1970 PST
459 | 509 | 00459_update9 | Sun Mar 01 00:00:00 1970 PST
460 | 0 | 00460 | Mon Mar 02 00:00:00 1970 PST
461 | 1 | 00461 | Tue Mar 03 00:00:00 1970 PST
463 | 303 | 00463_update3 | Thu Mar 05 00:00:00 1970 PST
464 | 4 | 00464 | Fri Mar 06 00:00:00 1970 PST
466 | 6 | 00466 | Sun Mar 08 00:00:00 1970 PST
467 | 407 | 00467_update7 | Mon Mar 09 00:00:00 1970 PST
468 | 8 | 00468 | Tue Mar 10 00:00:00 1970 PST
469 | 509 | 00469_update9 | Wed Mar 11 00:00:00 1970 PST
470 | 0 | 00470 | Thu Mar 12 00:00:00 1970 PST
471 | 1 | 00471 | Fri Mar 13 00:00:00 1970 PST
473 | 303 | 00473_update3 | Sun Mar 15 00:00:00 1970 PST
474 | 4 | 00474 | Mon Mar 16 00:00:00 1970 PST
476 | 6 | 00476 | Wed Mar 18 00:00:00 1970 PST
477 | 407 | 00477_update7 | Thu Mar 19 00:00:00 1970 PST
478 | 8 | 00478 | Fri Mar 20 00:00:00 1970 PST
479 | 509 | 00479_update9 | Sat Mar 21 00:00:00 1970 PST
480 | 0 | 00480 | Sun Mar 22 00:00:00 1970 PST
481 | 1 | 00481 | Mon Mar 23 00:00:00 1970 PST
483 | 303 | 00483_update3 | Wed Mar 25 00:00:00 1970 PST
484 | 4 | 00484 | Thu Mar 26 00:00:00 1970 PST
486 | 6 | 00486 | Sat Mar 28 00:00:00 1970 PST
487 | 407 | 00487_update7 | Sun Mar 29 00:00:00 1970 PST
488 | 8 | 00488 | Mon Mar 30 00:00:00 1970 PST
489 | 509 | 00489_update9 | Tue Mar 31 00:00:00 1970 PST
490 | 0 | 00490 | Wed Apr 01 00:00:00 1970 PST
491 | 1 | 00491 | Thu Apr 02 00:00:00 1970 PST
493 | 303 | 00493_update3 | Sat Apr 04 00:00:00 1970 PST
494 | 4 | 00494 | Sun Apr 05 00:00:00 1970 PST
496 | 6 | 00496 | Tue Apr 07 00:00:00 1970 PST
497 | 407 | 00497_update7 | Wed Apr 08 00:00:00 1970 PST
498 | 8 | 00498 | Thu Apr 09 00:00:00 1970 PST
499 | 509 | 00499_update9 | Fri Apr 10 00:00:00 1970 PST
500 | 0 | 00500 | Thu Jan 01 00:00:00 1970 PST
501 | 1 | 00501 | Fri Jan 02 00:00:00 1970 PST
503 | 303 | 00503_update3 | Sun Jan 04 00:00:00 1970 PST
504 | 4 | 00504 | Mon Jan 05 00:00:00 1970 PST
506 | 6 | 00506 | Wed Jan 07 00:00:00 1970 PST
507 | 407 | 00507_update7 | Thu Jan 08 00:00:00 1970 PST
508 | 8 | 00508 | Fri Jan 09 00:00:00 1970 PST
509 | 509 | 00509_update9 | Sat Jan 10 00:00:00 1970 PST
510 | 0 | 00510 | Sun Jan 11 00:00:00 1970 PST
511 | 1 | 00511 | Mon Jan 12 00:00:00 1970 PST
513 | 303 | 00513_update3 | Wed Jan 14 00:00:00 1970 PST
514 | 4 | 00514 | Thu Jan 15 00:00:00 1970 PST
516 | 6 | 00516 | Sat Jan 17 00:00:00 1970 PST
517 | 407 | 00517_update7 | Sun Jan 18 00:00:00 1970 PST
518 | 8 | 00518 | Mon Jan 19 00:00:00 1970 PST
519 | 509 | 00519_update9 | Tue Jan 20 00:00:00 1970 PST
520 | 0 | 00520 | Wed Jan 21 00:00:00 1970 PST
521 | 1 | 00521 | Thu Jan 22 00:00:00 1970 PST
523 | 303 | 00523_update3 | Sat Jan 24 00:00:00 1970 PST
524 | 4 | 00524 | Sun Jan 25 00:00:00 1970 PST
526 | 6 | 00526 | Tue Jan 27 00:00:00 1970 PST
527 | 407 | 00527_update7 | Wed Jan 28 00:00:00 1970 PST
528 | 8 | 00528 | Thu Jan 29 00:00:00 1970 PST
529 | 509 | 00529_update9 | Fri Jan 30 00:00:00 1970 PST
530 | 0 | 00530 | Sat Jan 31 00:00:00 1970 PST
531 | 1 | 00531 | Sun Feb 01 00:00:00 1970 PST
533 | 303 | 00533_update3 | Tue Feb 03 00:00:00 1970 PST
534 | 4 | 00534 | Wed Feb 04 00:00:00 1970 PST
536 | 6 | 00536 | Fri Feb 06 00:00:00 1970 PST
537 | 407 | 00537_update7 | Sat Feb 07 00:00:00 1970 PST
538 | 8 | 00538 | Sun Feb 08 00:00:00 1970 PST
539 | 509 | 00539_update9 | Mon Feb 09 00:00:00 1970 PST
540 | 0 | 00540 | Tue Feb 10 00:00:00 1970 PST
541 | 1 | 00541 | Wed Feb 11 00:00:00 1970 PST
543 | 303 | 00543_update3 | Fri Feb 13 00:00:00 1970 PST
544 | 4 | 00544 | Sat Feb 14 00:00:00 1970 PST
546 | 6 | 00546 | Mon Feb 16 00:00:00 1970 PST
547 | 407 | 00547_update7 | Tue Feb 17 00:00:00 1970 PST
548 | 8 | 00548 | Wed Feb 18 00:00:00 1970 PST
549 | 509 | 00549_update9 | Thu Feb 19 00:00:00 1970 PST
550 | 0 | 00550 | Fri Feb 20 00:00:00 1970 PST
551 | 1 | 00551 | Sat Feb 21 00:00:00 1970 PST
553 | 303 | 00553_update3 | Mon Feb 23 00:00:00 1970 PST
554 | 4 | 00554 | Tue Feb 24 00:00:00 1970 PST
556 | 6 | 00556 | Thu Feb 26 00:00:00 1970 PST
557 | 407 | 00557_update7 | Fri Feb 27 00:00:00 1970 PST
558 | 8 | 00558 | Sat Feb 28 00:00:00 1970 PST
559 | 509 | 00559_update9 | Sun Mar 01 00:00:00 1970 PST
560 | 0 | 00560 | Mon Mar 02 00:00:00 1970 PST
561 | 1 | 00561 | Tue Mar 03 00:00:00 1970 PST
563 | 303 | 00563_update3 | Thu Mar 05 00:00:00 1970 PST
564 | 4 | 00564 | Fri Mar 06 00:00:00 1970 PST
566 | 6 | 00566 | Sun Mar 08 00:00:00 1970 PST
567 | 407 | 00567_update7 | Mon Mar 09 00:00:00 1970 PST
568 | 8 | 00568 | Tue Mar 10 00:00:00 1970 PST
569 | 509 | 00569_update9 | Wed Mar 11 00:00:00 1970 PST
570 | 0 | 00570 | Thu Mar 12 00:00:00 1970 PST
571 | 1 | 00571 | Fri Mar 13 00:00:00 1970 PST
573 | 303 | 00573_update3 | Sun Mar 15 00:00:00 1970 PST
574 | 4 | 00574 | Mon Mar 16 00:00:00 1970 PST
576 | 6 | 00576 | Wed Mar 18 00:00:00 1970 PST
577 | 407 | 00577_update7 | Thu Mar 19 00:00:00 1970 PST
578 | 8 | 00578 | Fri Mar 20 00:00:00 1970 PST
579 | 509 | 00579_update9 | Sat Mar 21 00:00:00 1970 PST
580 | 0 | 00580 | Sun Mar 22 00:00:00 1970 PST
581 | 1 | 00581 | Mon Mar 23 00:00:00 1970 PST
583 | 303 | 00583_update3 | Wed Mar 25 00:00:00 1970 PST
584 | 4 | 00584 | Thu Mar 26 00:00:00 1970 PST
586 | 6 | 00586 | Sat Mar 28 00:00:00 1970 PST
587 | 407 | 00587_update7 | Sun Mar 29 00:00:00 1970 PST
588 | 8 | 00588 | Mon Mar 30 00:00:00 1970 PST
589 | 509 | 00589_update9 | Tue Mar 31 00:00:00 1970 PST
590 | 0 | 00590 | Wed Apr 01 00:00:00 1970 PST
591 | 1 | 00591 | Thu Apr 02 00:00:00 1970 PST
593 | 303 | 00593_update3 | Sat Apr 04 00:00:00 1970 PST
594 | 4 | 00594 | Sun Apr 05 00:00:00 1970 PST
596 | 6 | 00596 | Tue Apr 07 00:00:00 1970 PST
597 | 407 | 00597_update7 | Wed Apr 08 00:00:00 1970 PST
598 | 8 | 00598 | Thu Apr 09 00:00:00 1970 PST
599 | 509 | 00599_update9 | Fri Apr 10 00:00:00 1970 PST
600 | 0 | 00600 | Thu Jan 01 00:00:00 1970 PST
601 | 1 | 00601 | Fri Jan 02 00:00:00 1970 PST
603 | 303 | 00603_update3 | Sun Jan 04 00:00:00 1970 PST
604 | 4 | 00604 | Mon Jan 05 00:00:00 1970 PST
606 | 6 | 00606 | Wed Jan 07 00:00:00 1970 PST
607 | 407 | 00607_update7 | Thu Jan 08 00:00:00 1970 PST
608 | 8 | 00608 | Fri Jan 09 00:00:00 1970 PST
609 | 509 | 00609_update9 | Sat Jan 10 00:00:00 1970 PST
610 | 0 | 00610 | Sun Jan 11 00:00:00 1970 PST
611 | 1 | 00611 | Mon Jan 12 00:00:00 1970 PST
613 | 303 | 00613_update3 | Wed Jan 14 00:00:00 1970 PST
614 | 4 | 00614 | Thu Jan 15 00:00:00 1970 PST
616 | 6 | 00616 | Sat Jan 17 00:00:00 1970 PST
617 | 407 | 00617_update7 | Sun Jan 18 00:00:00 1970 PST
618 | 8 | 00618 | Mon Jan 19 00:00:00 1970 PST
619 | 509 | 00619_update9 | Tue Jan 20 00:00:00 1970 PST
620 | 0 | 00620 | Wed Jan 21 00:00:00 1970 PST
621 | 1 | 00621 | Thu Jan 22 00:00:00 1970 PST
623 | 303 | 00623_update3 | Sat Jan 24 00:00:00 1970 PST
624 | 4 | 00624 | Sun Jan 25 00:00:00 1970 PST
626 | 6 | 00626 | Tue Jan 27 00:00:00 1970 PST
627 | 407 | 00627_update7 | Wed Jan 28 00:00:00 1970 PST
628 | 8 | 00628 | Thu Jan 29 00:00:00 1970 PST
629 | 509 | 00629_update9 | Fri Jan 30 00:00:00 1970 PST
630 | 0 | 00630 | Sat Jan 31 00:00:00 1970 PST
631 | 1 | 00631 | Sun Feb 01 00:00:00 1970 PST
633 | 303 | 00633_update3 | Tue Feb 03 00:00:00 1970 PST
634 | 4 | 00634 | Wed Feb 04 00:00:00 1970 PST
636 | 6 | 00636 | Fri Feb 06 00:00:00 1970 PST
637 | 407 | 00637_update7 | Sat Feb 07 00:00:00 1970 PST
638 | 8 | 00638 | Sun Feb 08 00:00:00 1970 PST
639 | 509 | 00639_update9 | Mon Feb 09 00:00:00 1970 PST
640 | 0 | 00640 | Tue Feb 10 00:00:00 1970 PST
641 | 1 | 00641 | Wed Feb 11 00:00:00 1970 PST
643 | 303 | 00643_update3 | Fri Feb 13 00:00:00 1970 PST
644 | 4 | 00644 | Sat Feb 14 00:00:00 1970 PST
646 | 6 | 00646 | Mon Feb 16 00:00:00 1970 PST
647 | 407 | 00647_update7 | Tue Feb 17 00:00:00 1970 PST
648 | 8 | 00648 | Wed Feb 18 00:00:00 1970 PST
649 | 509 | 00649_update9 | Thu Feb 19 00:00:00 1970 PST
650 | 0 | 00650 | Fri Feb 20 00:00:00 1970 PST
651 | 1 | 00651 | Sat Feb 21 00:00:00 1970 PST
653 | 303 | 00653_update3 | Mon Feb 23 00:00:00 1970 PST
654 | 4 | 00654 | Tue Feb 24 00:00:00 1970 PST
656 | 6 | 00656 | Thu Feb 26 00:00:00 1970 PST
657 | 407 | 00657_update7 | Fri Feb 27 00:00:00 1970 PST
658 | 8 | 00658 | Sat Feb 28 00:00:00 1970 PST
659 | 509 | 00659_update9 | Sun Mar 01 00:00:00 1970 PST
660 | 0 | 00660 | Mon Mar 02 00:00:00 1970 PST
661 | 1 | 00661 | Tue Mar 03 00:00:00 1970 PST
663 | 303 | 00663_update3 | Thu Mar 05 00:00:00 1970 PST
664 | 4 | 00664 | Fri Mar 06 00:00:00 1970 PST
666 | 6 | 00666 | Sun Mar 08 00:00:00 1970 PST
667 | 407 | 00667_update7 | Mon Mar 09 00:00:00 1970 PST
668 | 8 | 00668 | Tue Mar 10 00:00:00 1970 PST
669 | 509 | 00669_update9 | Wed Mar 11 00:00:00 1970 PST
670 | 0 | 00670 | Thu Mar 12 00:00:00 1970 PST
671 | 1 | 00671 | Fri Mar 13 00:00:00 1970 PST
673 | 303 | 00673_update3 | Sun Mar 15 00:00:00 1970 PST
674 | 4 | 00674 | Mon Mar 16 00:00:00 1970 PST
676 | 6 | 00676 | Wed Mar 18 00:00:00 1970 PST
677 | 407 | 00677_update7 | Thu Mar 19 00:00:00 1970 PST
678 | 8 | 00678 | Fri Mar 20 00:00:00 1970 PST
679 | 509 | 00679_update9 | Sat Mar 21 00:00:00 1970 PST
680 | 0 | 00680 | Sun Mar 22 00:00:00 1970 PST
681 | 1 | 00681 | Mon Mar 23 00:00:00 1970 PST
683 | 303 | 00683_update3 | Wed Mar 25 00:00:00 1970 PST
684 | 4 | 00684 | Thu Mar 26 00:00:00 1970 PST
686 | 6 | 00686 | Sat Mar 28 00:00:00 1970 PST
687 | 407 | 00687_update7 | Sun Mar 29 00:00:00 1970 PST
688 | 8 | 00688 | Mon Mar 30 00:00:00 1970 PST
689 | 509 | 00689_update9 | Tue Mar 31 00:00:00 1970 PST
690 | 0 | 00690 | Wed Apr 01 00:00:00 1970 PST
691 | 1 | 00691 | Thu Apr 02 00:00:00 1970 PST
693 | 303 | 00693_update3 | Sat Apr 04 00:00:00 1970 PST
694 | 4 | 00694 | Sun Apr 05 00:00:00 1970 PST
696 | 6 | 00696 | Tue Apr 07 00:00:00 1970 PST
697 | 407 | 00697_update7 | Wed Apr 08 00:00:00 1970 PST
698 | 8 | 00698 | Thu Apr 09 00:00:00 1970 PST
699 | 509 | 00699_update9 | Fri Apr 10 00:00:00 1970 PST
700 | 0 | 00700 | Thu Jan 01 00:00:00 1970 PST
701 | 1 | 00701 | Fri Jan 02 00:00:00 1970 PST
703 | 303 | 00703_update3 | Sun Jan 04 00:00:00 1970 PST
704 | 4 | 00704 | Mon Jan 05 00:00:00 1970 PST
706 | 6 | 00706 | Wed Jan 07 00:00:00 1970 PST
707 | 407 | 00707_update7 | Thu Jan 08 00:00:00 1970 PST
708 | 8 | 00708 | Fri Jan 09 00:00:00 1970 PST
709 | 509 | 00709_update9 | Sat Jan 10 00:00:00 1970 PST
710 | 0 | 00710 | Sun Jan 11 00:00:00 1970 PST
711 | 1 | 00711 | Mon Jan 12 00:00:00 1970 PST
713 | 303 | 00713_update3 | Wed Jan 14 00:00:00 1970 PST
714 | 4 | 00714 | Thu Jan 15 00:00:00 1970 PST
716 | 6 | 00716 | Sat Jan 17 00:00:00 1970 PST
717 | 407 | 00717_update7 | Sun Jan 18 00:00:00 1970 PST
718 | 8 | 00718 | Mon Jan 19 00:00:00 1970 PST
719 | 509 | 00719_update9 | Tue Jan 20 00:00:00 1970 PST
720 | 0 | 00720 | Wed Jan 21 00:00:00 1970 PST
721 | 1 | 00721 | Thu Jan 22 00:00:00 1970 PST
723 | 303 | 00723_update3 | Sat Jan 24 00:00:00 1970 PST
724 | 4 | 00724 | Sun Jan 25 00:00:00 1970 PST
726 | 6 | 00726 | Tue Jan 27 00:00:00 1970 PST
727 | 407 | 00727_update7 | Wed Jan 28 00:00:00 1970 PST
728 | 8 | 00728 | Thu Jan 29 00:00:00 1970 PST
729 | 509 | 00729_update9 | Fri Jan 30 00:00:00 1970 PST
730 | 0 | 00730 | Sat Jan 31 00:00:00 1970 PST
731 | 1 | 00731 | Sun Feb 01 00:00:00 1970 PST
733 | 303 | 00733_update3 | Tue Feb 03 00:00:00 1970 PST
734 | 4 | 00734 | Wed Feb 04 00:00:00 1970 PST
736 | 6 | 00736 | Fri Feb 06 00:00:00 1970 PST
737 | 407 | 00737_update7 | Sat Feb 07 00:00:00 1970 PST
738 | 8 | 00738 | Sun Feb 08 00:00:00 1970 PST
739 | 509 | 00739_update9 | Mon Feb 09 00:00:00 1970 PST
740 | 0 | 00740 | Tue Feb 10 00:00:00 1970 PST
741 | 1 | 00741 | Wed Feb 11 00:00:00 1970 PST
743 | 303 | 00743_update3 | Fri Feb 13 00:00:00 1970 PST
744 | 4 | 00744 | Sat Feb 14 00:00:00 1970 PST
746 | 6 | 00746 | Mon Feb 16 00:00:00 1970 PST
747 | 407 | 00747_update7 | Tue Feb 17 00:00:00 1970 PST
748 | 8 | 00748 | Wed Feb 18 00:00:00 1970 PST
749 | 509 | 00749_update9 | Thu Feb 19 00:00:00 1970 PST
750 | 0 | 00750 | Fri Feb 20 00:00:00 1970 PST
751 | 1 | 00751 | Sat Feb 21 00:00:00 1970 PST
753 | 303 | 00753_update3 | Mon Feb 23 00:00:00 1970 PST
754 | 4 | 00754 | Tue Feb 24 00:00:00 1970 PST
756 | 6 | 00756 | Thu Feb 26 00:00:00 1970 PST
757 | 407 | 00757_update7 | Fri Feb 27 00:00:00 1970 PST
758 | 8 | 00758 | Sat Feb 28 00:00:00 1970 PST
759 | 509 | 00759_update9 | Sun Mar 01 00:00:00 1970 PST
760 | 0 | 00760 | Mon Mar 02 00:00:00 1970 PST
761 | 1 | 00761 | Tue Mar 03 00:00:00 1970 PST
763 | 303 | 00763_update3 | Thu Mar 05 00:00:00 1970 PST
764 | 4 | 00764 | Fri Mar 06 00:00:00 1970 PST
766 | 6 | 00766 | Sun Mar 08 00:00:00 1970 PST
767 | 407 | 00767_update7 | Mon Mar 09 00:00:00 1970 PST
768 | 8 | 00768 | Tue Mar 10 00:00:00 1970 PST
769 | 509 | 00769_update9 | Wed Mar 11 00:00:00 1970 PST
770 | 0 | 00770 | Thu Mar 12 00:00:00 1970 PST
771 | 1 | 00771 | Fri Mar 13 00:00:00 1970 PST
773 | 303 | 00773_update3 | Sun Mar 15 00:00:00 1970 PST
774 | 4 | 00774 | Mon Mar 16 00:00:00 1970 PST
776 | 6 | 00776 | Wed Mar 18 00:00:00 1970 PST
777 | 407 | 00777_update7 | Thu Mar 19 00:00:00 1970 PST
778 | 8 | 00778 | Fri Mar 20 00:00:00 1970 PST
779 | 509 | 00779_update9 | Sat Mar 21 00:00:00 1970 PST
780 | 0 | 00780 | Sun Mar 22 00:00:00 1970 PST
781 | 1 | 00781 | Mon Mar 23 00:00:00 1970 PST
783 | 303 | 00783_update3 | Wed Mar 25 00:00:00 1970 PST
784 | 4 | 00784 | Thu Mar 26 00:00:00 1970 PST
786 | 6 | 00786 | Sat Mar 28 00:00:00 1970 PST
787 | 407 | 00787_update7 | Sun Mar 29 00:00:00 1970 PST
788 | 8 | 00788 | Mon Mar 30 00:00:00 1970 PST
789 | 509 | 00789_update9 | Tue Mar 31 00:00:00 1970 PST
790 | 0 | 00790 | Wed Apr 01 00:00:00 1970 PST
791 | 1 | 00791 | Thu Apr 02 00:00:00 1970 PST
793 | 303 | 00793_update3 | Sat Apr 04 00:00:00 1970 PST
794 | 4 | 00794 | Sun Apr 05 00:00:00 1970 PST
796 | 6 | 00796 | Tue Apr 07 00:00:00 1970 PST
797 | 407 | 00797_update7 | Wed Apr 08 00:00:00 1970 PST
798 | 8 | 00798 | Thu Apr 09 00:00:00 1970 PST
799 | 509 | 00799_update9 | Fri Apr 10 00:00:00 1970 PST
800 | 0 | 00800 | Thu Jan 01 00:00:00 1970 PST
801 | 1 | 00801 | Fri Jan 02 00:00:00 1970 PST
803 | 303 | 00803_update3 | Sun Jan 04 00:00:00 1970 PST
804 | 4 | 00804 | Mon Jan 05 00:00:00 1970 PST
806 | 6 | 00806 | Wed Jan 07 00:00:00 1970 PST
807 | 407 | 00807_update7 | Thu Jan 08 00:00:00 1970 PST
808 | 8 | 00808 | Fri Jan 09 00:00:00 1970 PST
809 | 509 | 00809_update9 | Sat Jan 10 00:00:00 1970 PST
810 | 0 | 00810 | Sun Jan 11 00:00:00 1970 PST
811 | 1 | 00811 | Mon Jan 12 00:00:00 1970 PST
813 | 303 | 00813_update3 | Wed Jan 14 00:00:00 1970 PST
814 | 4 | 00814 | Thu Jan 15 00:00:00 1970 PST
816 | 6 | 00816 | Sat Jan 17 00:00:00 1970 PST
817 | 407 | 00817_update7 | Sun Jan 18 00:00:00 1970 PST
818 | 8 | 00818 | Mon Jan 19 00:00:00 1970 PST
819 | 509 | 00819_update9 | Tue Jan 20 00:00:00 1970 PST
820 | 0 | 00820 | Wed Jan 21 00:00:00 1970 PST
821 | 1 | 00821 | Thu Jan 22 00:00:00 1970 PST
823 | 303 | 00823_update3 | Sat Jan 24 00:00:00 1970 PST
824 | 4 | 00824 | Sun Jan 25 00:00:00 1970 PST
826 | 6 | 00826 | Tue Jan 27 00:00:00 1970 PST
827 | 407 | 00827_update7 | Wed Jan 28 00:00:00 1970 PST
828 | 8 | 00828 | Thu Jan 29 00:00:00 1970 PST
829 | 509 | 00829_update9 | Fri Jan 30 00:00:00 1970 PST
830 | 0 | 00830 | Sat Jan 31 00:00:00 1970 PST
831 | 1 | 00831 | Sun Feb 01 00:00:00 1970 PST
833 | 303 | 00833_update3 | Tue Feb 03 00:00:00 1970 PST
834 | 4 | 00834 | Wed Feb 04 00:00:00 1970 PST
836 | 6 | 00836 | Fri Feb 06 00:00:00 1970 PST
837 | 407 | 00837_update7 | Sat Feb 07 00:00:00 1970 PST
838 | 8 | 00838 | Sun Feb 08 00:00:00 1970 PST
839 | 509 | 00839_update9 | Mon Feb 09 00:00:00 1970 PST
840 | 0 | 00840 | Tue Feb 10 00:00:00 1970 PST
841 | 1 | 00841 | Wed Feb 11 00:00:00 1970 PST
843 | 303 | 00843_update3 | Fri Feb 13 00:00:00 1970 PST
844 | 4 | 00844 | Sat Feb 14 00:00:00 1970 PST
846 | 6 | 00846 | Mon Feb 16 00:00:00 1970 PST
847 | 407 | 00847_update7 | Tue Feb 17 00:00:00 1970 PST
848 | 8 | 00848 | Wed Feb 18 00:00:00 1970 PST
849 | 509 | 00849_update9 | Thu Feb 19 00:00:00 1970 PST
850 | 0 | 00850 | Fri Feb 20 00:00:00 1970 PST
851 | 1 | 00851 | Sat Feb 21 00:00:00 1970 PST
853 | 303 | 00853_update3 | Mon Feb 23 00:00:00 1970 PST
854 | 4 | 00854 | Tue Feb 24 00:00:00 1970 PST
856 | 6 | 00856 | Thu Feb 26 00:00:00 1970 PST
857 | 407 | 00857_update7 | Fri Feb 27 00:00:00 1970 PST
858 | 8 | 00858 | Sat Feb 28 00:00:00 1970 PST
859 | 509 | 00859_update9 | Sun Mar 01 00:00:00 1970 PST
860 | 0 | 00860 | Mon Mar 02 00:00:00 1970 PST
861 | 1 | 00861 | Tue Mar 03 00:00:00 1970 PST
863 | 303 | 00863_update3 | Thu Mar 05 00:00:00 1970 PST
864 | 4 | 00864 | Fri Mar 06 00:00:00 1970 PST
866 | 6 | 00866 | Sun Mar 08 00:00:00 1970 PST
867 | 407 | 00867_update7 | Mon Mar 09 00:00:00 1970 PST
868 | 8 | 00868 | Tue Mar 10 00:00:00 1970 PST
869 | 509 | 00869_update9 | Wed Mar 11 00:00:00 1970 PST
870 | 0 | 00870 | Thu Mar 12 00:00:00 1970 PST
871 | 1 | 00871 | Fri Mar 13 00:00:00 1970 PST
873 | 303 | 00873_update3 | Sun Mar 15 00:00:00 1970 PST
874 | 4 | 00874 | Mon Mar 16 00:00:00 1970 PST
876 | 6 | 00876 | Wed Mar 18 00:00:00 1970 PST
877 | 407 | 00877_update7 | Thu Mar 19 00:00:00 1970 PST
878 | 8 | 00878 | Fri Mar 20 00:00:00 1970 PST
879 | 509 | 00879_update9 | Sat Mar 21 00:00:00 1970 PST
880 | 0 | 00880 | Sun Mar 22 00:00:00 1970 PST
881 | 1 | 00881 | Mon Mar 23 00:00:00 1970 PST
883 | 303 | 00883_update3 | Wed Mar 25 00:00:00 1970 PST
884 | 4 | 00884 | Thu Mar 26 00:00:00 1970 PST
886 | 6 | 00886 | Sat Mar 28 00:00:00 1970 PST
887 | 407 | 00887_update7 | Sun Mar 29 00:00:00 1970 PST
888 | 8 | 00888 | Mon Mar 30 00:00:00 1970 PST
889 | 509 | 00889_update9 | Tue Mar 31 00:00:00 1970 PST
890 | 0 | 00890 | Wed Apr 01 00:00:00 1970 PST
891 | 1 | 00891 | Thu Apr 02 00:00:00 1970 PST
893 | 303 | 00893_update3 | Sat Apr 04 00:00:00 1970 PST
894 | 4 | 00894 | Sun Apr 05 00:00:00 1970 PST
896 | 6 | 00896 | Tue Apr 07 00:00:00 1970 PST
897 | 407 | 00897_update7 | Wed Apr 08 00:00:00 1970 PST
898 | 8 | 00898 | Thu Apr 09 00:00:00 1970 PST
899 | 509 | 00899_update9 | Fri Apr 10 00:00:00 1970 PST
900 | 0 | 00900 | Thu Jan 01 00:00:00 1970 PST
901 | 1 | 00901 | Fri Jan 02 00:00:00 1970 PST
903 | 303 | 00903_update3 | Sun Jan 04 00:00:00 1970 PST
904 | 4 | 00904 | Mon Jan 05 00:00:00 1970 PST
906 | 6 | 00906 | Wed Jan 07 00:00:00 1970 PST
907 | 407 | 00907_update7 | Thu Jan 08 00:00:00 1970 PST
908 | 8 | 00908 | Fri Jan 09 00:00:00 1970 PST
909 | 509 | 00909_update9 | Sat Jan 10 00:00:00 1970 PST
910 | 0 | 00910 | Sun Jan 11 00:00:00 1970 PST
911 | 1 | 00911 | Mon Jan 12 00:00:00 1970 PST
913 | 303 | 00913_update3 | Wed Jan 14 00:00:00 1970 PST
914 | 4 | 00914 | Thu Jan 15 00:00:00 1970 PST
916 | 6 | 00916 | Sat Jan 17 00:00:00 1970 PST
917 | 407 | 00917_update7 | Sun Jan 18 00:00:00 1970 PST
918 | 8 | 00918 | Mon Jan 19 00:00:00 1970 PST
919 | 509 | 00919_update9 | Tue Jan 20 00:00:00 1970 PST
920 | 0 | 00920 | Wed Jan 21 00:00:00 1970 PST
921 | 1 | 00921 | Thu Jan 22 00:00:00 1970 PST
923 | 303 | 00923_update3 | Sat Jan 24 00:00:00 1970 PST
924 | 4 | 00924 | Sun Jan 25 00:00:00 1970 PST
926 | 6 | 00926 | Tue Jan 27 00:00:00 1970 PST
927 | 407 | 00927_update7 | Wed Jan 28 00:00:00 1970 PST
928 | 8 | 00928 | Thu Jan 29 00:00:00 1970 PST
929 | 509 | 00929_update9 | Fri Jan 30 00:00:00 1970 PST
930 | 0 | 00930 | Sat Jan 31 00:00:00 1970 PST
931 | 1 | 00931 | Sun Feb 01 00:00:00 1970 PST
933 | 303 | 00933_update3 | Tue Feb 03 00:00:00 1970 PST
934 | 4 | 00934 | Wed Feb 04 00:00:00 1970 PST
936 | 6 | 00936 | Fri Feb 06 00:00:00 1970 PST
937 | 407 | 00937_update7 | Sat Feb 07 00:00:00 1970 PST
938 | 8 | 00938 | Sun Feb 08 00:00:00 1970 PST
939 | 509 | 00939_update9 | Mon Feb 09 00:00:00 1970 PST
940 | 0 | 00940 | Tue Feb 10 00:00:00 1970 PST
941 | 1 | 00941 | Wed Feb 11 00:00:00 1970 PST
943 | 303 | 00943_update3 | Fri Feb 13 00:00:00 1970 PST
944 | 4 | 00944 | Sat Feb 14 00:00:00 1970 PST
946 | 6 | 00946 | Mon Feb 16 00:00:00 1970 PST
947 | 407 | 00947_update7 | Tue Feb 17 00:00:00 1970 PST
948 | 8 | 00948 | Wed Feb 18 00:00:00 1970 PST
949 | 509 | 00949_update9 | Thu Feb 19 00:00:00 1970 PST
950 | 0 | 00950 | Fri Feb 20 00:00:00 1970 PST
951 | 1 | 00951 | Sat Feb 21 00:00:00 1970 PST
953 | 303 | 00953_update3 | Mon Feb 23 00:00:00 1970 PST
954 | 4 | 00954 | Tue Feb 24 00:00:00 1970 PST
956 | 6 | 00956 | Thu Feb 26 00:00:00 1970 PST
957 | 407 | 00957_update7 | Fri Feb 27 00:00:00 1970 PST
958 | 8 | 00958 | Sat Feb 28 00:00:00 1970 PST
959 | 509 | 00959_update9 | Sun Mar 01 00:00:00 1970 PST
960 | 0 | 00960 | Mon Mar 02 00:00:00 1970 PST
961 | 1 | 00961 | Tue Mar 03 00:00:00 1970 PST
963 | 303 | 00963_update3 | Thu Mar 05 00:00:00 1970 PST
964 | 4 | 00964 | Fri Mar 06 00:00:00 1970 PST
966 | 6 | 00966 | Sun Mar 08 00:00:00 1970 PST
967 | 407 | 00967_update7 | Mon Mar 09 00:00:00 1970 PST
968 | 8 | 00968 | Tue Mar 10 00:00:00 1970 PST
969 | 509 | 00969_update9 | Wed Mar 11 00:00:00 1970 PST
970 | 0 | 00970 | Thu Mar 12 00:00:00 1970 PST
971 | 1 | 00971 | Fri Mar 13 00:00:00 1970 PST
973 | 303 | 00973_update3 | Sun Mar 15 00:00:00 1970 PST
974 | 4 | 00974 | Mon Mar 16 00:00:00 1970 PST
976 | 6 | 00976 | Wed Mar 18 00:00:00 1970 PST
977 | 407 | 00977_update7 | Thu Mar 19 00:00:00 1970 PST
978 | 8 | 00978 | Fri Mar 20 00:00:00 1970 PST
979 | 509 | 00979_update9 | Sat Mar 21 00:00:00 1970 PST
980 | 0 | 00980 | Sun Mar 22 00:00:00 1970 PST
981 | 1 | 00981 | Mon Mar 23 00:00:00 1970 PST
983 | 303 | 00983_update3 | Wed Mar 25 00:00:00 1970 PST
984 | 4 | 00984 | Thu Mar 26 00:00:00 1970 PST
986 | 6 | 00986 | Sat Mar 28 00:00:00 1970 PST
987 | 407 | 00987_update7 | Sun Mar 29 00:00:00 1970 PST
988 | 8 | 00988 | Mon Mar 30 00:00:00 1970 PST
989 | 509 | 00989_update9 | Tue Mar 31 00:00:00 1970 PST
990 | 0 | 00990 | Wed Apr 01 00:00:00 1970 PST
991 | 1 | 00991 | Thu Apr 02 00:00:00 1970 PST
993 | 303 | 00993_update3 | Sat Apr 04 00:00:00 1970 PST
994 | 4 | 00994 | Sun Apr 05 00:00:00 1970 PST
996 | 6 | 00996 | Tue Apr 07 00:00:00 1970 PST
997 | 407 | 00997_update7 | Wed Apr 08 00:00:00 1970 PST
998 | 8 | 00998 | Thu Apr 09 00:00:00 1970 PST
999 | 509 | 00999_update9 | Fri Apr 10 00:00:00 1970 PST
1000 | 0 | 01000 | Thu Jan 01 00:00:00 1970 PST
1001 | 101 | 0000100001 |
1003 | 403 | 0000300003_update3 |
1004 | 104 | 0000400004 |
1006 | 106 | 0000600006 |
1007 | 507 | 0000700007_update7 |
1008 | 108 | 0000800008 |
1009 | 609 | 0000900009_update9 |
1010 | 100 | 0001000010 |
1011 | 101 | 0001100011 |
1013 | 403 | 0001300013_update3 |
1014 | 104 | 0001400014 |
1016 | 106 | 0001600016 |
1017 | 507 | 0001700017_update7 |
1018 | 108 | 0001800018 |
1019 | 609 | 0001900019_update9 |
1020 | 100 | 0002000020 |
1101 | 201 | aaa |
1103 | 503 | ccc_update3 |
1104 | 204 | ddd |
(819 rows)
-- 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 *;
c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8
------+-----+-----------------+----+----+--------+----+----
1208 | 218 | fff_trig_update | | | (^-^;) | |
(1 row)
INSERT INTO ft2 (c1,c2,c3,c6) VALUES (1218, 218, 'ggg', '(--;') RETURNING *;
c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8
------+-----+-----------------+----+----+------+----+----
1218 | 218 | ggg_trig_update | | | (--; | |
(1 row)
UPDATE ft2 SET c2 = c2 + 600 WHERE c1 % 10 = 8 RETURNING *;
c1 | c2 | c3 | c4 | c5 | c6 | c7 | c8
------+-----+-----------------------------+------------------------------+--------------------------+--------+------------+-----
8 | 608 | 00008_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo
18 | 608 | 00018_trig_update | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo
28 | 608 | 00028_trig_update | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8 | 8 | foo
38 | 608 | 00038_trig_update | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8 | 8 | foo
48 | 608 | 00048_trig_update | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8 | 8 | foo
58 | 608 | 00058_trig_update | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8 | 8 | foo
68 | 608 | 00068_trig_update | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8 | 8 | foo
78 | 608 | 00078_trig_update | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8 | 8 | foo
88 | 608 | 00088_trig_update | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8 | 8 | foo
98 | 608 | 00098_trig_update | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8 | 8 | foo
108 | 608 | 00108_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo
118 | 608 | 00118_trig_update | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo
128 | 608 | 00128_trig_update | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8 | 8 | foo
138 | 608 | 00138_trig_update | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8 | 8 | foo
148 | 608 | 00148_trig_update | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8 | 8 | foo
158 | 608 | 00158_trig_update | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8 | 8 | foo
168 | 608 | 00168_trig_update | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8 | 8 | foo
178 | 608 | 00178_trig_update | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8 | 8 | foo
188 | 608 | 00188_trig_update | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8 | 8 | foo
198 | 608 | 00198_trig_update | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8 | 8 | foo
208 | 608 | 00208_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo
218 | 608 | 00218_trig_update | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo
228 | 608 | 00228_trig_update | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8 | 8 | foo
238 | 608 | 00238_trig_update | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8 | 8 | foo
248 | 608 | 00248_trig_update | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8 | 8 | foo
258 | 608 | 00258_trig_update | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8 | 8 | foo
268 | 608 | 00268_trig_update | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8 | 8 | foo
278 | 608 | 00278_trig_update | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8 | 8 | foo
288 | 608 | 00288_trig_update | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8 | 8 | foo
298 | 608 | 00298_trig_update | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8 | 8 | foo
308 | 608 | 00308_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo
318 | 608 | 00318_trig_update | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo
328 | 608 | 00328_trig_update | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8 | 8 | foo
338 | 608 | 00338_trig_update | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8 | 8 | foo
348 | 608 | 00348_trig_update | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8 | 8 | foo
358 | 608 | 00358_trig_update | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8 | 8 | foo
368 | 608 | 00368_trig_update | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8 | 8 | foo
378 | 608 | 00378_trig_update | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8 | 8 | foo
388 | 608 | 00388_trig_update | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8 | 8 | foo
398 | 608 | 00398_trig_update | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8 | 8 | foo
408 | 608 | 00408_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo
418 | 608 | 00418_trig_update | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo
428 | 608 | 00428_trig_update | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8 | 8 | foo
438 | 608 | 00438_trig_update | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8 | 8 | foo
448 | 608 | 00448_trig_update | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8 | 8 | foo
458 | 608 | 00458_trig_update | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8 | 8 | foo
468 | 608 | 00468_trig_update | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8 | 8 | foo
478 | 608 | 00478_trig_update | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8 | 8 | foo
488 | 608 | 00488_trig_update | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8 | 8 | foo
498 | 608 | 00498_trig_update | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8 | 8 | foo
508 | 608 | 00508_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo
518 | 608 | 00518_trig_update | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo
528 | 608 | 00528_trig_update | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8 | 8 | foo
538 | 608 | 00538_trig_update | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8 | 8 | foo
548 | 608 | 00548_trig_update | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8 | 8 | foo
558 | 608 | 00558_trig_update | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8 | 8 | foo
568 | 608 | 00568_trig_update | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8 | 8 | foo
578 | 608 | 00578_trig_update | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8 | 8 | foo
588 | 608 | 00588_trig_update | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8 | 8 | foo
598 | 608 | 00598_trig_update | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8 | 8 | foo
608 | 608 | 00608_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo
618 | 608 | 00618_trig_update | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo
628 | 608 | 00628_trig_update | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8 | 8 | foo
638 | 608 | 00638_trig_update | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8 | 8 | foo
648 | 608 | 00648_trig_update | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8 | 8 | foo
658 | 608 | 00658_trig_update | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8 | 8 | foo
668 | 608 | 00668_trig_update | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8 | 8 | foo
678 | 608 | 00678_trig_update | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8 | 8 | foo
688 | 608 | 00688_trig_update | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8 | 8 | foo
698 | 608 | 00698_trig_update | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8 | 8 | foo
708 | 608 | 00708_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo
718 | 608 | 00718_trig_update | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo
728 | 608 | 00728_trig_update | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8 | 8 | foo
738 | 608 | 00738_trig_update | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8 | 8 | foo
748 | 608 | 00748_trig_update | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8 | 8 | foo
758 | 608 | 00758_trig_update | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8 | 8 | foo
768 | 608 | 00768_trig_update | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8 | 8 | foo
778 | 608 | 00778_trig_update | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8 | 8 | foo
788 | 608 | 00788_trig_update | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8 | 8 | foo
798 | 608 | 00798_trig_update | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8 | 8 | foo
808 | 608 | 00808_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo
818 | 608 | 00818_trig_update | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo
828 | 608 | 00828_trig_update | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8 | 8 | foo
838 | 608 | 00838_trig_update | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8 | 8 | foo
848 | 608 | 00848_trig_update | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8 | 8 | foo
858 | 608 | 00858_trig_update | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8 | 8 | foo
868 | 608 | 00868_trig_update | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8 | 8 | foo
878 | 608 | 00878_trig_update | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8 | 8 | foo
888 | 608 | 00888_trig_update | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8 | 8 | foo
898 | 608 | 00898_trig_update | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8 | 8 | foo
908 | 608 | 00908_trig_update | Fri Jan 09 00:00:00 1970 PST | Fri Jan 09 00:00:00 1970 | 8 | 8 | foo
918 | 608 | 00918_trig_update | Mon Jan 19 00:00:00 1970 PST | Mon Jan 19 00:00:00 1970 | 8 | 8 | foo
928 | 608 | 00928_trig_update | Thu Jan 29 00:00:00 1970 PST | Thu Jan 29 00:00:00 1970 | 8 | 8 | foo
938 | 608 | 00938_trig_update | Sun Feb 08 00:00:00 1970 PST | Sun Feb 08 00:00:00 1970 | 8 | 8 | foo
948 | 608 | 00948_trig_update | Wed Feb 18 00:00:00 1970 PST | Wed Feb 18 00:00:00 1970 | 8 | 8 | foo
958 | 608 | 00958_trig_update | Sat Feb 28 00:00:00 1970 PST | Sat Feb 28 00:00:00 1970 | 8 | 8 | foo
968 | 608 | 00968_trig_update | Tue Mar 10 00:00:00 1970 PST | Tue Mar 10 00:00:00 1970 | 8 | 8 | foo
978 | 608 | 00978_trig_update | Fri Mar 20 00:00:00 1970 PST | Fri Mar 20 00:00:00 1970 | 8 | 8 | foo
988 | 608 | 00988_trig_update | Mon Mar 30 00:00:00 1970 PST | Mon Mar 30 00:00:00 1970 | 8 | 8 | foo
998 | 608 | 00998_trig_update | Thu Apr 09 00:00:00 1970 PST | Thu Apr 09 00:00:00 1970 | 8 | 8 | foo
1008 | 708 | 0000800008_trig_update | | | | |
1018 | 708 | 0001800018_trig_update | | | | |
1208 | 818 | fff_trig_update_trig_update | | | (^-^;) | |
1218 | 818 | ggg_trig_update_trig_update | | | (--; | |
(104 rows)
-- 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
ERROR: duplicate key value violates unique constraint "t1_pkey"
DETAIL: Key ("C 1")=(11) already exists.
CONTEXT: Remote SQL command: INSERT INTO "S 1"."T 1"("C 1", c2) VALUES ($1, $2)
INSERT INTO ft1(c1, c2) VALUES(1111, -2); -- c2positive
ERROR: new row for relation "T 1" violates check constraint "c2positive"
DETAIL: Failing row contains (1111, -2, null, null, null, (^-^;), null, null).
CONTEXT: Remote SQL command: INSERT INTO "S 1"."T 1"("C 1", c2) VALUES ($1, $2)
UPDATE ft1 SET c2 = -c2 WHERE c1 = 1; -- c2positive
ERROR: new row for relation "T 1" violates check constraint "c2positive"
DETAIL: Failing row contains (1, -1, 00001_trig_update, 1970-01-02 03:00:00-05, 1970-01-02 00:00:00, 1, 1 , foo).
CONTEXT: Remote SQL command: UPDATE "S 1"."T 1" SET c2 = $2 WHERE ctid = $1
-- Test savepoint/rollback behavior
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
c2 | count
-----+-------
0 | 100
1 | 100
4 | 100
6 | 100
100 | 2
101 | 2
104 | 2
106 | 2
201 | 1
204 | 1
303 | 100
403 | 2
407 | 100
(13 rows)
select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1;
c2 | count
-----+-------
0 | 100
1 | 100
4 | 100
6 | 100
100 | 2
101 | 2
104 | 2
106 | 2
201 | 1
204 | 1
303 | 100
403 | 2
407 | 100
(13 rows)
begin;
update ft2 set c2 = 42 where c2 = 0;
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
c2 | count
-----+-------
1 | 100
4 | 100
6 | 100
42 | 100
100 | 2
101 | 2
104 | 2
106 | 2
201 | 1
204 | 1
303 | 100
403 | 2
407 | 100
(13 rows)
savepoint s1;
update ft2 set c2 = 44 where c2 = 4;
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
c2 | count
-----+-------
1 | 100
6 | 100
42 | 100
44 | 100
100 | 2
101 | 2
104 | 2
106 | 2
201 | 1
204 | 1
303 | 100
403 | 2
407 | 100
(13 rows)
release savepoint s1;
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
c2 | count
-----+-------
1 | 100
6 | 100
42 | 100
44 | 100
100 | 2
101 | 2
104 | 2
106 | 2
201 | 1
204 | 1
303 | 100
403 | 2
407 | 100
(13 rows)
savepoint s2;
update ft2 set c2 = 46 where c2 = 6;
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
c2 | count
-----+-------
1 | 100
42 | 100
44 | 100
46 | 100
100 | 2
101 | 2
104 | 2
106 | 2
201 | 1
204 | 1
303 | 100
403 | 2
407 | 100
(13 rows)
rollback to savepoint s2;
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
c2 | count
-----+-------
1 | 100
6 | 100
42 | 100
44 | 100
100 | 2
101 | 2
104 | 2
106 | 2
201 | 1
204 | 1
303 | 100
403 | 2
407 | 100
(13 rows)
release savepoint s2;
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
c2 | count
-----+-------
1 | 100
6 | 100
42 | 100
44 | 100
100 | 2
101 | 2
104 | 2
106 | 2
201 | 1
204 | 1
303 | 100
403 | 2
407 | 100
(13 rows)
savepoint s3;
update ft2 set c2 = -2 where c2 = 42; -- fail on remote side
ERROR: new row for relation "T 1" violates check constraint "c2positive"
DETAIL: Failing row contains (10, -2, 00010_trig_update_trig_update, 1970-01-11 03:00:00-05, 1970-01-11 00:00:00, 0, 0 , foo).
CONTEXT: Remote SQL command: UPDATE "S 1"."T 1" SET c2 = $2 WHERE ctid = $1
rollback to savepoint s3;
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
c2 | count
-----+-------
1 | 100
6 | 100
42 | 100
44 | 100
100 | 2
101 | 2
104 | 2
106 | 2
201 | 1
204 | 1
303 | 100
403 | 2
407 | 100
(13 rows)
release savepoint s3;
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
c2 | count
-----+-------
1 | 100
6 | 100
42 | 100
44 | 100
100 | 2
101 | 2
104 | 2
106 | 2
201 | 1
204 | 1
303 | 100
403 | 2
407 | 100
(13 rows)
-- 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;
c2 | count
-----+-------
0 | 100
1 | 100
4 | 100
6 | 100
100 | 2
101 | 2
104 | 2
106 | 2
201 | 1
204 | 1
303 | 100
403 | 2
407 | 100
(13 rows)
commit;
select c2, count(*) from ft2 where c2 < 500 group by 1 order by 1;
c2 | count
-----+-------
1 | 100
6 | 100
42 | 100
44 | 100
100 | 2
101 | 2
104 | 2
106 | 2
201 | 1
204 | 1
303 | 100
403 | 2
407 | 100
(13 rows)
select c2, count(*) from "S 1"."T 1" where c2 < 500 group by 1 order by 1;
c2 | count
-----+-------
1 | 100
6 | 100
42 | 100
44 | 100
100 | 2
101 | 2
104 | 2
106 | 2
201 | 1
204 | 1
303 | 100
403 | 2
407 | 100
(13 rows)
......@@ -15,16 +15,21 @@
#include "postgres_fdw.h"
#include "access/htup_details.h"
#include "access/sysattr.h"
#include "commands/defrem.h"
#include "commands/explain.h"
#include "commands/vacuum.h"
#include "foreign/fdwapi.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "optimizer/cost.h"
#include "optimizer/pathnode.h"
#include "optimizer/planmain.h"
#include "optimizer/prep.h"
#include "optimizer/var.h"
#include "parser/parsetree.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
......@@ -58,7 +63,7 @@ typedef struct PgFdwRelationInfo
} PgFdwRelationInfo;
/*
* Indexes of FDW-private information stored in fdw_private list.
* Indexes of FDW-private information stored in fdw_private lists.
*
* We store various information in ForeignScan.fdw_private to pass it from
* planner to executor. Specifically there is:
......@@ -66,26 +71,41 @@ typedef struct PgFdwRelationInfo
* 1) SELECT statement text to be sent to the remote server
* 2) IDs of PARAM_EXEC Params used in the SELECT statement
*
* These items are indexed with the enum FdwPrivateIndex, so an item can be
* fetched with list_nth(). For example, to get the SELECT statement:
* sql = strVal(list_nth(fdw_private, FdwPrivateSelectSql));
* These items are indexed with the enum FdwScanPrivateIndex, so an item
* can be fetched with list_nth(). For example, to get the SELECT statement:
* sql = strVal(list_nth(fdw_private, FdwScanPrivateSelectSql));
*/
enum FdwPrivateIndex
enum FdwScanPrivateIndex
{
/* SQL statement to execute remotely (as a String node) */
FdwPrivateSelectSql,
FdwScanPrivateSelectSql,
/* Integer list of param IDs of PARAM_EXEC Params used in SQL stmt */
FdwPrivateExternParamIds,
FdwScanPrivateExternParamIds
};
/* # of elements stored in the list fdw_private */
FdwPrivateNum
/*
* Similarly, this enum describes what's kept in the fdw_private list for
* a ModifyTable node referencing a postgres_fdw foreign table. We store:
*
* 1) INSERT/UPDATE/DELETE statement text to be sent to the remote server
* 2) Integer list of target attribute numbers for INSERT/UPDATE
* (NIL for a DELETE)
* 3) Boolean flag showing if there's a RETURNING clause
*/
enum FdwModifyPrivateIndex
{
/* SQL statement to execute remotely (as a String node) */
FdwModifyPrivateUpdateSql,
/* Integer list of target attribute numbers for INSERT/UPDATE */
FdwModifyPrivateTargetAttnums,
/* has-returning flag (as an integer Value node) */
FdwModifyPrivateHasReturning
};
/*
* Execution state of a foreign scan using postgres_fdw.
*/
typedef struct PgFdwExecutionState
typedef struct PgFdwScanState
{
Relation rel; /* relcache entry for the foreign table */
AttInMetadata *attinmeta; /* attribute datatype conversion metadata */
......@@ -113,7 +133,33 @@ typedef struct PgFdwExecutionState
/* working memory contexts */
MemoryContext batch_cxt; /* context holding current batch of tuples */
MemoryContext temp_cxt; /* context for per-tuple temporary data */
} PgFdwExecutionState;
} PgFdwScanState;
/*
* Execution state of a foreign insert/update/delete operation.
*/
typedef struct PgFdwModifyState
{
Relation rel; /* relcache entry for the foreign table */
AttInMetadata *attinmeta; /* attribute datatype conversion metadata */
/* for remote query execution */
PGconn *conn; /* connection for the scan */
char *p_name; /* name of prepared statement, if created */
/* extracted fdw_private data */
char *query; /* text of INSERT/UPDATE/DELETE command */
List *target_attrs; /* list of target attribute numbers */
bool has_returning; /* is there a RETURNING clause? */
/* info about parameters for prepared statement */
AttrNumber ctidAttno; /* attnum of input resjunk ctid column */
int p_nums; /* number of parameters to transmit */
FmgrInfo *p_flinfo; /* output conversion functions for them */
/* working memory context */
MemoryContext temp_cxt; /* context for per-tuple temporary data */
} PgFdwModifyState;
/*
* Workspace for analyzing a foreign table.
......@@ -169,12 +215,43 @@ static ForeignScan *postgresGetForeignPlan(PlannerInfo *root,
ForeignPath *best_path,
List *tlist,
List *scan_clauses);
static void postgresExplainForeignScan(ForeignScanState *node,
ExplainState *es);
static void postgresBeginForeignScan(ForeignScanState *node, int eflags);
static TupleTableSlot *postgresIterateForeignScan(ForeignScanState *node);
static void postgresReScanForeignScan(ForeignScanState *node);
static void postgresEndForeignScan(ForeignScanState *node);
static void postgresAddForeignUpdateTargets(Query *parsetree,
RangeTblEntry *target_rte,
Relation target_relation);
static List *postgresPlanForeignModify(PlannerInfo *root,
ModifyTable *plan,
Index resultRelation,
int subplan_index);
static void postgresBeginForeignModify(ModifyTableState *mtstate,
ResultRelInfo *resultRelInfo,
List *fdw_private,
int subplan_index,
int eflags);
static TupleTableSlot *postgresExecForeignInsert(EState *estate,
ResultRelInfo *resultRelInfo,
TupleTableSlot *slot,
TupleTableSlot *planSlot);
static TupleTableSlot *postgresExecForeignUpdate(EState *estate,
ResultRelInfo *resultRelInfo,
TupleTableSlot *slot,
TupleTableSlot *planSlot);
static TupleTableSlot *postgresExecForeignDelete(EState *estate,
ResultRelInfo *resultRelInfo,
TupleTableSlot *slot,
TupleTableSlot *planSlot);
static void postgresEndForeignModify(EState *estate,
ResultRelInfo *resultRelInfo);
static void postgresExplainForeignScan(ForeignScanState *node,
ExplainState *es);
static void postgresExplainForeignModify(ModifyTableState *mtstate,
ResultRelInfo *rinfo,
List *fdw_private,
int subplan_index,
ExplainState *es);
static bool postgresAnalyzeForeignTable(Relation relation,
AcquireSampleRowsFunc *func,
BlockNumber *totalpages);
......@@ -191,6 +268,12 @@ static void get_remote_estimate(const char *sql,
static void create_cursor(ForeignScanState *node);
static void fetch_more_data(ForeignScanState *node);
static void close_cursor(PGconn *conn, unsigned int cursor_number);
static void prepare_foreign_modify(PgFdwModifyState *fmstate);
static const char **convert_prep_stmt_params(PgFdwModifyState *fmstate,
ItemPointer tupleid,
TupleTableSlot *slot);
static void store_returning_result(PgFdwModifyState *fmstate,
TupleTableSlot *slot, PGresult *res);
static int postgresAcquireSampleRowsFunc(Relation relation, int elevel,
HeapTuple *rows, int targrows,
double *totalrows,
......@@ -214,17 +297,29 @@ postgres_fdw_handler(PG_FUNCTION_ARGS)
{
FdwRoutine *routine = makeNode(FdwRoutine);
/* Required handler functions. */
/* Functions for scanning foreign tables */
routine->GetForeignRelSize = postgresGetForeignRelSize;
routine->GetForeignPaths = postgresGetForeignPaths;
routine->GetForeignPlan = postgresGetForeignPlan;
routine->ExplainForeignScan = postgresExplainForeignScan;
routine->BeginForeignScan = postgresBeginForeignScan;
routine->IterateForeignScan = postgresIterateForeignScan;
routine->ReScanForeignScan = postgresReScanForeignScan;
routine->EndForeignScan = postgresEndForeignScan;
/* Optional handler functions. */
/* Functions for updating foreign tables */
routine->AddForeignUpdateTargets = postgresAddForeignUpdateTargets;
routine->PlanForeignModify = postgresPlanForeignModify;
routine->BeginForeignModify = postgresBeginForeignModify;
routine->ExecForeignInsert = postgresExecForeignInsert;
routine->ExecForeignUpdate = postgresExecForeignUpdate;
routine->ExecForeignDelete = postgresExecForeignDelete;
routine->EndForeignModify = postgresEndForeignModify;
/* Support functions for EXPLAIN */
routine->ExplainForeignScan = postgresExplainForeignScan;
routine->ExplainForeignModify = postgresExplainForeignModify;
/* Support functions for ANALYZE */
routine->AnalyzeForeignTable = postgresAnalyzeForeignTable;
PG_RETURN_POINTER(routine);
......@@ -249,7 +344,6 @@ postgresGetForeignRelSize(PlannerInfo *root,
Oid foreigntableid)
{
bool use_remote_estimate = false;
ListCell *lc;
PgFdwRelationInfo *fpinfo;
StringInfo sql;
ForeignTable *table;
......@@ -266,12 +360,14 @@ postgresGetForeignRelSize(PlannerInfo *root,
List *param_conds;
List *local_conds;
List *param_numbers;
Bitmapset *attrs_used;
ListCell *lc;
/*
* We use PgFdwRelationInfo to pass various information to subsequent
* functions.
*/
fpinfo = palloc0(sizeof(PgFdwRelationInfo));
fpinfo = (PgFdwRelationInfo *) palloc0(sizeof(PgFdwRelationInfo));
initStringInfo(&fpinfo->sql);
sql = &fpinfo->sql;
......@@ -303,16 +399,37 @@ postgresGetForeignRelSize(PlannerInfo *root,
}
/*
* Construct remote query which consists of SELECT, FROM, and WHERE
* clauses. Conditions which contain any Param node are excluded because
* placeholder can't be used in EXPLAIN statement. Such conditions are
* appended later.
* Identify which restriction clauses can be sent to the remote server and
* which can't. Conditions that are remotely executable but contain
* PARAM_EXTERN Params have to be treated separately because we can't use
* placeholders in remote EXPLAIN.
*/
classifyConditions(root, baserel, &remote_conds, &param_conds,
&local_conds, &param_numbers);
deparseSimpleSql(sql, root, baserel, local_conds);
if (list_length(remote_conds) > 0)
appendWhereClause(sql, true, remote_conds, root);
/*
* Identify which attributes will need to be retrieved from the remote
* server. These include all attrs needed for joins or final output, plus
* all attrs used in the local_conds.
*/
attrs_used = NULL;
pull_varattnos((Node *) baserel->reltargetlist, baserel->relid,
&attrs_used);
foreach(lc, local_conds)
{
RestrictInfo *rinfo = (RestrictInfo *) lfirst(lc);
pull_varattnos((Node *) rinfo->clause, baserel->relid,
&attrs_used);
}
/*
* Construct remote query which consists of SELECT, FROM, and WHERE
* clauses. For now, leave out the param_conds.
*/
deparseSelectSql(sql, root, baserel, attrs_used);
if (remote_conds)
appendWhereClause(sql, root, remote_conds, true);
/*
* If the table or the server is configured to use remote estimates,
......@@ -336,7 +453,7 @@ postgresGetForeignRelSize(PlannerInfo *root,
userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
user = GetUserMapping(userid, server->serverid);
conn = GetConnection(server, user);
conn = GetConnection(server, user, false);
get_remote_estimate(sql->data, conn, &rows, &width,
&startup_cost, &total_cost);
ReleaseConnection(conn);
......@@ -403,11 +520,53 @@ postgresGetForeignRelSize(PlannerInfo *root,
/*
* Finish deparsing remote query by adding conditions which were unusable
* in remote EXPLAIN since they contain Param nodes.
* in remote EXPLAIN because they contain Param nodes.
*/
if (list_length(param_conds) > 0)
appendWhereClause(sql, !(list_length(remote_conds) > 0), param_conds,
root);
if (param_conds)
appendWhereClause(sql, root, param_conds, (remote_conds == NIL));
/*
* Add FOR UPDATE/SHARE if appropriate. We apply locking during the
* initial row fetch, rather than later on as is done for local tables.
* The extra roundtrips involved in trying to duplicate the local
* semantics exactly don't seem worthwhile (see also comments for
* RowMarkType).
*/
if (baserel->relid == root->parse->resultRelation &&
(root->parse->commandType == CMD_UPDATE ||
root->parse->commandType == CMD_DELETE))
{
/* Relation is UPDATE/DELETE target, so use FOR UPDATE */
appendStringInfo(sql, " FOR UPDATE");
}
else
{
RowMarkClause *rc = get_parse_rowmark(root->parse, baserel->relid);
if (rc)
{
/*
* Relation is specified as a FOR UPDATE/SHARE target, so handle
* that.
*
* For now, just ignore any [NO] KEY specification, since (a) it's
* not clear what that means for a remote table that we don't have
* complete information about, and (b) it wouldn't work anyway on
* older remote servers. Likewise, we don't worry about NOWAIT.
*/
switch (rc->strength)
{
case LCS_FORKEYSHARE:
case LCS_FORSHARE:
appendStringInfo(sql, " FOR SHARE");
break;
case LCS_FORNOKEYUPDATE:
case LCS_FORUPDATE:
appendStringInfo(sql, " FOR UPDATE");
break;
}
}
}
/*
* Store obtained information into FDW-private area of RelOptInfo so it's
......@@ -477,7 +636,7 @@ postgresGetForeignPaths(PlannerInfo *root,
/*
* Build the fdw_private list that will be available to the executor.
* Items in the list must match enum FdwPrivateIndex, above.
* Items in the list must match enum FdwScanPrivateIndex, above.
*/
fdw_private = list_make2(makeString(fpinfo->sql.data),
fpinfo->param_numbers);
......@@ -573,24 +732,6 @@ postgresGetForeignPlan(PlannerInfo *root,
fdw_private);
}
/*
* postgresExplainForeignScan
* Produce extra output for EXPLAIN
*/
static void
postgresExplainForeignScan(ForeignScanState *node, ExplainState *es)
{
List *fdw_private;
char *sql;
if (es->verbose)
{
fdw_private = ((ForeignScan *) node->ss.ps.plan)->fdw_private;
sql = strVal(list_nth(fdw_private, FdwPrivateSelectSql));
ExplainPropertyText("Remote SQL", sql, es);
}
}
/*
* postgresBeginForeignScan
* Initiate an executor scan of a foreign PostgreSQL table.
......@@ -600,7 +741,7 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
{
ForeignScan *fsplan = (ForeignScan *) node->ss.ps.plan;
EState *estate = node->ss.ps.state;
PgFdwExecutionState *festate;
PgFdwScanState *fsstate;
RangeTblEntry *rte;
Oid userid;
ForeignTable *table;
......@@ -619,8 +760,8 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
/*
* We'll save private state in node->fdw_state.
*/
festate = (PgFdwExecutionState *) palloc0(sizeof(PgFdwExecutionState));
node->fdw_state = (void *) festate;
fsstate = (PgFdwScanState *) palloc0(sizeof(PgFdwScanState));
node->fdw_state = (void *) fsstate;
/*
* Identify which user to do the remote access as. This should match what
......@@ -630,8 +771,8 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
/* Get info about foreign table. */
festate->rel = node->ss.ss_currentRelation;
table = GetForeignTable(RelationGetRelid(festate->rel));
fsstate->rel = node->ss.ss_currentRelation;
table = GetForeignTable(RelationGetRelid(fsstate->rel));
server = GetForeignServer(table->serverid);
user = GetUserMapping(userid, server->serverid);
......@@ -639,29 +780,29 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
* Get connection to the foreign server. Connection manager will
* establish new connection if necessary.
*/
festate->conn = GetConnection(server, user);
fsstate->conn = GetConnection(server, user, false);
/* Assign a unique ID for my cursor */
festate->cursor_number = GetCursorNumber(festate->conn);
festate->cursor_exists = false;
fsstate->cursor_number = GetCursorNumber(fsstate->conn);
fsstate->cursor_exists = false;
/* Get private info created by planner functions. */
festate->fdw_private = fsplan->fdw_private;
fsstate->fdw_private = fsplan->fdw_private;
/* Create contexts for batches of tuples and per-tuple temp workspace. */
festate->batch_cxt = AllocSetContextCreate(estate->es_query_cxt,
fsstate->batch_cxt = AllocSetContextCreate(estate->es_query_cxt,
"postgres_fdw tuple data",
ALLOCSET_DEFAULT_MINSIZE,
ALLOCSET_DEFAULT_INITSIZE,
ALLOCSET_DEFAULT_MAXSIZE);
festate->temp_cxt = AllocSetContextCreate(estate->es_query_cxt,
fsstate->temp_cxt = AllocSetContextCreate(estate->es_query_cxt,
"postgres_fdw temporary data",
ALLOCSET_SMALL_MINSIZE,
ALLOCSET_SMALL_INITSIZE,
ALLOCSET_SMALL_MAXSIZE);
/* Get info we'll need for data conversion. */
festate->attinmeta = TupleDescGetAttInMetadata(RelationGetDescr(festate->rel));
fsstate->attinmeta = TupleDescGetAttInMetadata(RelationGetDescr(fsstate->rel));
/*
* Allocate buffer for query parameters, if the remote conditions use any.
......@@ -673,7 +814,7 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
* null values that are arbitrarily marked as being of type int4.
*/
param_numbers = (List *)
list_nth(festate->fdw_private, FdwPrivateExternParamIds);
list_nth(fsstate->fdw_private, FdwScanPrivateExternParamIds);
if (param_numbers != NIL)
{
ParamListInfo params = estate->es_param_list_info;
......@@ -682,21 +823,21 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
}
else
numParams = 0;
festate->numParams = numParams;
fsstate->numParams = numParams;
if (numParams > 0)
{
/* we initially fill all slots with value = NULL, type = int4 */
festate->param_types = (Oid *) palloc(numParams * sizeof(Oid));
festate->param_values = (const char **) palloc0(numParams * sizeof(char *));
fsstate->param_types = (Oid *) palloc(numParams * sizeof(Oid));
fsstate->param_values = (const char **) palloc0(numParams * sizeof(char *));
for (i = 0; i < numParams; i++)
festate->param_types[i] = INT4OID;
fsstate->param_types[i] = INT4OID;
}
else
{
festate->param_types = NULL;
festate->param_values = NULL;
fsstate->param_types = NULL;
fsstate->param_values = NULL;
}
festate->extparams_done = false;
fsstate->extparams_done = false;
}
/*
......@@ -707,33 +848,33 @@ postgresBeginForeignScan(ForeignScanState *node, int eflags)
static TupleTableSlot *
postgresIterateForeignScan(ForeignScanState *node)
{
PgFdwExecutionState *festate = (PgFdwExecutionState *) node->fdw_state;
PgFdwScanState *fsstate = (PgFdwScanState *) node->fdw_state;
TupleTableSlot *slot = node->ss.ss_ScanTupleSlot;
/*
* If this is the first call after Begin or ReScan, we need to create the
* cursor on the remote side.
*/
if (!festate->cursor_exists)
if (!fsstate->cursor_exists)
create_cursor(node);
/*
* Get some more tuples, if we've run out.
*/
if (festate->next_tuple >= festate->num_tuples)
if (fsstate->next_tuple >= fsstate->num_tuples)
{
/* No point in another fetch if we already detected EOF, though. */
if (!festate->eof_reached)
if (!fsstate->eof_reached)
fetch_more_data(node);
/* If we didn't get any tuples, must be end of data. */
if (festate->next_tuple >= festate->num_tuples)
if (fsstate->next_tuple >= fsstate->num_tuples)
return ExecClearTuple(slot);
}
/*
* Return the next tuple.
*/
ExecStoreTuple(festate->tuples[festate->next_tuple++],
ExecStoreTuple(fsstate->tuples[fsstate->next_tuple++],
slot,
InvalidBuffer,
false);
......@@ -748,7 +889,7 @@ postgresIterateForeignScan(ForeignScanState *node)
static void
postgresReScanForeignScan(ForeignScanState *node)
{
PgFdwExecutionState *festate = (PgFdwExecutionState *) node->fdw_state;
PgFdwScanState *fsstate = (PgFdwScanState *) node->fdw_state;
char sql[64];
PGresult *res;
......@@ -758,7 +899,7 @@ postgresReScanForeignScan(ForeignScanState *node)
*/
/* If we haven't created the cursor yet, nothing to do. */
if (!festate->cursor_exists)
if (!fsstate->cursor_exists)
return;
/*
......@@ -769,19 +910,19 @@ postgresReScanForeignScan(ForeignScanState *node)
*/
if (node->ss.ps.chgParam != NULL)
{
festate->cursor_exists = false;
fsstate->cursor_exists = false;
snprintf(sql, sizeof(sql), "CLOSE c%u",
festate->cursor_number);
fsstate->cursor_number);
}
else if (festate->fetch_ct_2 > 1)
else if (fsstate->fetch_ct_2 > 1)
{
snprintf(sql, sizeof(sql), "MOVE BACKWARD ALL IN c%u",
festate->cursor_number);
fsstate->cursor_number);
}
else
{
/* Easy: just rescan what we already have in memory, if anything */
festate->next_tuple = 0;
fsstate->next_tuple = 0;
return;
}
......@@ -789,17 +930,17 @@ postgresReScanForeignScan(ForeignScanState *node)
* We don't use a PG_TRY block here, so be careful not to throw error
* without releasing the PGresult.
*/
res = PQexec(festate->conn, sql);
res = PQexec(fsstate->conn, sql);
if (PQresultStatus(res) != PGRES_COMMAND_OK)
pgfdw_report_error(ERROR, res, true, sql);
PQclear(res);
/* Now force a fresh FETCH. */
festate->tuples = NULL;
festate->num_tuples = 0;
festate->next_tuple = 0;
festate->fetch_ct_2 = 0;
festate->eof_reached = false;
fsstate->tuples = NULL;
fsstate->num_tuples = 0;
fsstate->next_tuple = 0;
fsstate->fetch_ct_2 = 0;
fsstate->eof_reached = false;
}
/*
......@@ -809,23 +950,529 @@ postgresReScanForeignScan(ForeignScanState *node)
static void
postgresEndForeignScan(ForeignScanState *node)
{
PgFdwExecutionState *festate = (PgFdwExecutionState *) node->fdw_state;
PgFdwScanState *fsstate = (PgFdwScanState *) node->fdw_state;
/* if festate is NULL, we are in EXPLAIN; nothing to do */
if (festate == NULL)
/* if fsstate is NULL, we are in EXPLAIN; nothing to do */
if (fsstate == NULL)
return;
/* Close the cursor if open, to prevent accumulation of cursors */
if (festate->cursor_exists)
close_cursor(festate->conn, festate->cursor_number);
if (fsstate->cursor_exists)
close_cursor(fsstate->conn, fsstate->cursor_number);
/* Release remote connection */
ReleaseConnection(festate->conn);
festate->conn = NULL;
ReleaseConnection(fsstate->conn);
fsstate->conn = NULL;
/* MemoryContexts will be deleted automatically. */
}
/*
* postgresAddForeignUpdateTargets
* Add resjunk column(s) needed for update/delete on a foreign table
*/
static void
postgresAddForeignUpdateTargets(Query *parsetree,
RangeTblEntry *target_rte,
Relation target_relation)
{
Var *var;
const char *attrname;
TargetEntry *tle;
/*
* In postgres_fdw, what we need is the ctid, same as for a regular table.
*/
/* Make a Var representing the desired value */
var = makeVar(parsetree->resultRelation,
SelfItemPointerAttributeNumber,
TIDOID,
-1,
InvalidOid,
0);
/* Wrap it in a resjunk TLE with the right name ... */
attrname = "ctid";
tle = makeTargetEntry((Expr *) var,
list_length(parsetree->targetList) + 1,
pstrdup(attrname),
true);
/* ... and add it to the query's targetlist */
parsetree->targetList = lappend(parsetree->targetList, tle);
}
/*
* postgresPlanForeignModify
* Plan an insert/update/delete operation on a foreign table
*
* Note: currently, the plan tree generated for UPDATE/DELETE will always
* include a ForeignScan that retrieves ctids (using SELECT FOR UPDATE)
* and then the ModifyTable node will have to execute individual remote
* UPDATE/DELETE commands. If there are no local conditions or joins
* needed, it'd be better to let the scan node do UPDATE/DELETE RETURNING
* and then do nothing at ModifyTable. Room for future optimization ...
*/
static List *
postgresPlanForeignModify(PlannerInfo *root,
ModifyTable *plan,
Index resultRelation,
int subplan_index)
{
CmdType operation = plan->operation;
StringInfoData sql;
List *targetAttrs = NIL;
List *returningList = NIL;
initStringInfo(&sql);
/*
* Construct a list of the columns that are to be assigned during INSERT
* or UPDATE. We should transmit only these columns, for performance and
* to respect any DEFAULT values the remote side may have for other
* columns. (XXX this will need some re-thinking when we support default
* expressions for foreign tables.)
*/
if (operation == CMD_INSERT || operation == CMD_UPDATE)
{
RangeTblEntry *rte = planner_rt_fetch(resultRelation, root);
Bitmapset *tmpset = bms_copy(rte->modifiedCols);
AttrNumber col;
while ((col = bms_first_member(tmpset)) >= 0)
{
col += FirstLowInvalidHeapAttributeNumber;
if (col <= InvalidAttrNumber) /* shouldn't happen */
elog(ERROR, "system-column update is not supported");
targetAttrs = lappend_int(targetAttrs, col);
}
}
/*
* Extract the relevant RETURNING list if any.
*/
if (plan->returningLists)
returningList = (List *) list_nth(plan->returningLists, subplan_index);
/*
* Construct the SQL command string.
*/
switch (operation)
{
case CMD_INSERT:
deparseInsertSql(&sql, root, resultRelation,
targetAttrs, returningList);
break;
case CMD_UPDATE:
deparseUpdateSql(&sql, root, resultRelation,
targetAttrs, returningList);
break;
case CMD_DELETE:
deparseDeleteSql(&sql, root, resultRelation, returningList);
break;
default:
elog(ERROR, "unexpected operation: %d", (int) operation);
break;
}
/*
* Build the fdw_private list that will be available to the executor.
* Items in the list must match enum FdwModifyPrivateIndex, above.
*/
return list_make3(makeString(sql.data),
targetAttrs,
makeInteger((returningList != NIL)));
}
/*
* postgresBeginForeignModify
* Begin an insert/update/delete operation on a foreign table
*/
static void
postgresBeginForeignModify(ModifyTableState *mtstate,
ResultRelInfo *resultRelInfo,
List *fdw_private,
int subplan_index,
int eflags)
{
PgFdwModifyState *fmstate;
EState *estate = mtstate->ps.state;
CmdType operation = mtstate->operation;
Relation rel = resultRelInfo->ri_RelationDesc;
RangeTblEntry *rte;
Oid userid;
ForeignTable *table;
ForeignServer *server;
UserMapping *user;
AttrNumber n_params;
Oid typefnoid;
bool isvarlena;
ListCell *lc;
/*
* Do nothing in EXPLAIN (no ANALYZE) case. resultRelInfo->ri_FdwState
* stays NULL.
*/
if (eflags & EXEC_FLAG_EXPLAIN_ONLY)
return;
/* Begin constructing PgFdwModifyState. */
fmstate = (PgFdwModifyState *) palloc0(sizeof(PgFdwModifyState));
fmstate->rel = rel;
/*
* Identify which user to do the remote access as. This should match what
* ExecCheckRTEPerms() does.
*/
rte = rt_fetch(resultRelInfo->ri_RangeTableIndex, estate->es_range_table);
userid = rte->checkAsUser ? rte->checkAsUser : GetUserId();
/* Get info about foreign table. */
table = GetForeignTable(RelationGetRelid(rel));
server = GetForeignServer(table->serverid);
user = GetUserMapping(userid, server->serverid);
/* Open connection; report that we'll create a prepared statement. */
fmstate->conn = GetConnection(server, user, true);
fmstate->p_name = NULL; /* prepared statement not made yet */
/* Deconstruct fdw_private data. */
fmstate->query = strVal(list_nth(fdw_private,
FdwModifyPrivateUpdateSql));
fmstate->target_attrs = (List *) list_nth(fdw_private,
FdwModifyPrivateTargetAttnums);
fmstate->has_returning = intVal(list_nth(fdw_private,
FdwModifyPrivateHasReturning));
/* Create context for per-tuple temp workspace. */
fmstate->temp_cxt = AllocSetContextCreate(estate->es_query_cxt,
"postgres_fdw temporary data",
ALLOCSET_SMALL_MINSIZE,
ALLOCSET_SMALL_INITSIZE,
ALLOCSET_SMALL_MAXSIZE);
/* Prepare for input conversion of RETURNING results. */
if (fmstate->has_returning)
fmstate->attinmeta = TupleDescGetAttInMetadata(RelationGetDescr(rel));
/* Prepare for output conversion of parameters used in prepared stmt. */
n_params = list_length(fmstate->target_attrs) + 1;
fmstate->p_flinfo = (FmgrInfo *) palloc0(sizeof(FmgrInfo) * n_params);
fmstate->p_nums = 0;
if (operation == CMD_UPDATE || operation == CMD_DELETE)
{
/* Find the ctid resjunk column in the subplan's result */
Plan *subplan = mtstate->mt_plans[subplan_index]->plan;
fmstate->ctidAttno = ExecFindJunkAttributeInTlist(subplan->targetlist,
"ctid");
if (!AttributeNumberIsValid(fmstate->ctidAttno))
elog(ERROR, "could not find junk ctid column");
/* First transmittable parameter will be ctid */
getTypeOutputInfo(TIDOID, &typefnoid, &isvarlena);
fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]);
fmstate->p_nums++;
}
if (operation == CMD_INSERT || operation == CMD_UPDATE)
{
/* Set up for remaining transmittable parameters */
foreach(lc, fmstate->target_attrs)
{
int attnum = lfirst_int(lc);
Form_pg_attribute attr = RelationGetDescr(rel)->attrs[attnum - 1];
Assert(!attr->attisdropped);
getTypeOutputInfo(attr->atttypid, &typefnoid, &isvarlena);
fmgr_info(typefnoid, &fmstate->p_flinfo[fmstate->p_nums]);
fmstate->p_nums++;
}
}
Assert(fmstate->p_nums <= n_params);
resultRelInfo->ri_FdwState = fmstate;
}
/*
* postgresExecForeignInsert
* Insert one row into a foreign table
*/
static TupleTableSlot *
postgresExecForeignInsert(EState *estate,
ResultRelInfo *resultRelInfo,
TupleTableSlot *slot,
TupleTableSlot *planSlot)
{
PgFdwModifyState *fmstate = (PgFdwModifyState *) resultRelInfo->ri_FdwState;
const char **p_values;
PGresult *res;
int n_rows;
/* Set up the prepared statement on the remote server, if we didn't yet */
if (!fmstate->p_name)
prepare_foreign_modify(fmstate);
/* Convert parameters needed by prepared statement to text form */
p_values = convert_prep_stmt_params(fmstate, NULL, slot);
/*
* Execute the prepared statement, and check for success.
*
* We don't use a PG_TRY block here, so be careful not to throw error
* without releasing the PGresult.
*/
res = PQexecPrepared(fmstate->conn,
fmstate->p_name,
fmstate->p_nums,
p_values,
NULL,
NULL,
0);
if (PQresultStatus(res) !=
(fmstate->has_returning ? PGRES_TUPLES_OK : PGRES_COMMAND_OK))
pgfdw_report_error(ERROR, res, true, fmstate->query);
/* Check number of rows affected, and fetch RETURNING tuple if any */
if (fmstate->has_returning)
{
n_rows = PQntuples(res);
if (n_rows > 0)
store_returning_result(fmstate, slot, res);
}
else
n_rows = atoi(PQcmdTuples(res));
/* And clean up */
PQclear(res);
MemoryContextReset(fmstate->temp_cxt);
/* Return NULL if nothing was inserted on the remote end */
return (n_rows > 0) ? slot : NULL;
}
/*
* postgresExecForeignUpdate
* Update one row in a foreign table
*/
static TupleTableSlot *
postgresExecForeignUpdate(EState *estate,
ResultRelInfo *resultRelInfo,
TupleTableSlot *slot,
TupleTableSlot *planSlot)
{
PgFdwModifyState *fmstate = (PgFdwModifyState *) resultRelInfo->ri_FdwState;
Datum datum;
bool isNull;
const char **p_values;
PGresult *res;
int n_rows;
/* Set up the prepared statement on the remote server, if we didn't yet */
if (!fmstate->p_name)
prepare_foreign_modify(fmstate);
/* Get the ctid that was passed up as a resjunk column */
datum = ExecGetJunkAttribute(planSlot,
fmstate->ctidAttno,
&isNull);
/* shouldn't ever get a null result... */
if (isNull)
elog(ERROR, "ctid is NULL");
/* Convert parameters needed by prepared statement to text form */
p_values = convert_prep_stmt_params(fmstate,
(ItemPointer) DatumGetPointer(datum),
slot);
/*
* Execute the prepared statement, and check for success.
*
* We don't use a PG_TRY block here, so be careful not to throw error
* without releasing the PGresult.
*/
res = PQexecPrepared(fmstate->conn,
fmstate->p_name,
fmstate->p_nums,
p_values,
NULL,
NULL,
0);
if (PQresultStatus(res) !=
(fmstate->has_returning ? PGRES_TUPLES_OK : PGRES_COMMAND_OK))
pgfdw_report_error(ERROR, res, true, fmstate->query);
/* Check number of rows affected, and fetch RETURNING tuple if any */
if (fmstate->has_returning)
{
n_rows = PQntuples(res);
if (n_rows > 0)
store_returning_result(fmstate, slot, res);
}
else
n_rows = atoi(PQcmdTuples(res));
/* And clean up */
PQclear(res);
MemoryContextReset(fmstate->temp_cxt);
/* Return NULL if nothing was updated on the remote end */
return (n_rows > 0) ? slot : NULL;
}
/*
* postgresExecForeignDelete
* Delete one row from a foreign table
*/
static TupleTableSlot *
postgresExecForeignDelete(EState *estate,
ResultRelInfo *resultRelInfo,
TupleTableSlot *slot,
TupleTableSlot *planSlot)
{
PgFdwModifyState *fmstate = (PgFdwModifyState *) resultRelInfo->ri_FdwState;
Datum datum;
bool isNull;
const char **p_values;
PGresult *res;
int n_rows;
/* Set up the prepared statement on the remote server, if we didn't yet */
if (!fmstate->p_name)
prepare_foreign_modify(fmstate);
/* Get the ctid that was passed up as a resjunk column */
datum = ExecGetJunkAttribute(planSlot,
fmstate->ctidAttno,
&isNull);
/* shouldn't ever get a null result... */
if (isNull)
elog(ERROR, "ctid is NULL");
/* Convert parameters needed by prepared statement to text form */
p_values = convert_prep_stmt_params(fmstate,
(ItemPointer) DatumGetPointer(datum),
NULL);
/*
* Execute the prepared statement, and check for success.
*
* We don't use a PG_TRY block here, so be careful not to throw error
* without releasing the PGresult.
*/
res = PQexecPrepared(fmstate->conn,
fmstate->p_name,
fmstate->p_nums,
p_values,
NULL,
NULL,
0);
if (PQresultStatus(res) !=
(fmstate->has_returning ? PGRES_TUPLES_OK : PGRES_COMMAND_OK))
pgfdw_report_error(ERROR, res, true, fmstate->query);
/* Check number of rows affected, and fetch RETURNING tuple if any */
if (fmstate->has_returning)
{
n_rows = PQntuples(res);
if (n_rows > 0)
store_returning_result(fmstate, slot, res);
}
else
n_rows = atoi(PQcmdTuples(res));
/* And clean up */
PQclear(res);
MemoryContextReset(fmstate->temp_cxt);
/* Return NULL if nothing was deleted on the remote end */
return (n_rows > 0) ? slot : NULL;
}
/*
* postgresEndForeignModify
* Finish an insert/update/delete operation on a foreign table
*/
static void
postgresEndForeignModify(EState *estate,
ResultRelInfo *resultRelInfo)
{
PgFdwModifyState *fmstate = (PgFdwModifyState *) resultRelInfo->ri_FdwState;
/* If fmstate is NULL, we are in EXPLAIN; nothing to do */
if (fmstate == NULL)
return;
/* If we created a prepared statement, destroy it */
if (fmstate->p_name)
{
char sql[64];
PGresult *res;
snprintf(sql, sizeof(sql), "DEALLOCATE %s", fmstate->p_name);
/*
* We don't use a PG_TRY block here, so be careful not to throw error
* without releasing the PGresult.
*/
res = PQexec(fmstate->conn, sql);
if (PQresultStatus(res) != PGRES_COMMAND_OK)
pgfdw_report_error(ERROR, res, true, sql);
PQclear(res);
fmstate->p_name = NULL;
}
/* Release remote connection */
ReleaseConnection(fmstate->conn);
fmstate->conn = NULL;
}
/*
* postgresExplainForeignScan
* Produce extra output for EXPLAIN of a ForeignScan on a foreign table
*/
static void
postgresExplainForeignScan(ForeignScanState *node, ExplainState *es)
{
List *fdw_private;
char *sql;
if (es->verbose)
{
fdw_private = ((ForeignScan *) node->ss.ps.plan)->fdw_private;
sql = strVal(list_nth(fdw_private, FdwScanPrivateSelectSql));
ExplainPropertyText("Remote SQL", sql, es);
}
}
/*
* postgresExplainForeignModify
* Produce extra output for EXPLAIN of a ModifyTable on a foreign table
*/
static void
postgresExplainForeignModify(ModifyTableState *mtstate,
ResultRelInfo *rinfo,
List *fdw_private,
int subplan_index,
ExplainState *es)
{
if (es->verbose)
{
char *sql = strVal(list_nth(fdw_private,
FdwModifyPrivateUpdateSql));
ExplainPropertyText("Remote SQL", sql, es);
}
}
/*
* Estimate costs of executing given SQL statement.
*/
......@@ -885,11 +1532,11 @@ get_remote_estimate(const char *sql, PGconn *conn,
static void
create_cursor(ForeignScanState *node)
{
PgFdwExecutionState *festate = (PgFdwExecutionState *) node->fdw_state;
int numParams = festate->numParams;
Oid *types = festate->param_types;
const char **values = festate->param_values;
PGconn *conn = festate->conn;
PgFdwScanState *fsstate = (PgFdwScanState *) node->fdw_state;
int numParams = fsstate->numParams;
Oid *types = fsstate->param_types;
const char **values = fsstate->param_values;
PGconn *conn = fsstate->conn;
char *sql;
StringInfoData buf;
PGresult *res;
......@@ -904,14 +1551,14 @@ create_cursor(ForeignScanState *node)
* recreate the cursor after a rescan, so we could need to re-use the
* values anyway.
*/
if (numParams > 0 && !festate->extparams_done)
if (numParams > 0 && !fsstate->extparams_done)
{
ParamListInfo params = node->ss.ps.state->es_param_list_info;
List *param_numbers;
ListCell *lc;
param_numbers = (List *)
list_nth(festate->fdw_private, FdwPrivateExternParamIds);
list_nth(fsstate->fdw_private, FdwScanPrivateExternParamIds);
foreach(lc, param_numbers)
{
int paramno = lfirst_int(lc);
......@@ -929,8 +1576,8 @@ create_cursor(ForeignScanState *node)
* same OIDs we do for the parameters' types.
*
* We'd not need to pass a type array to PQexecParams at all,
* except that there may be unused holes in the array, which
* will have to be filled with something or the remote server will
* except that there may be unused holes in the array, which will
* have to be filled with something or the remote server will
* complain. We arbitrarily set them to INT4OID earlier.
*/
types[paramno - 1] = InvalidOid;
......@@ -951,14 +1598,14 @@ create_cursor(ForeignScanState *node)
prm->value);
}
}
festate->extparams_done = true;
fsstate->extparams_done = true;
}
/* Construct the DECLARE CURSOR command */
sql = strVal(list_nth(festate->fdw_private, FdwPrivateSelectSql));
sql = strVal(list_nth(fsstate->fdw_private, FdwScanPrivateSelectSql));
initStringInfo(&buf);
appendStringInfo(&buf, "DECLARE c%u CURSOR FOR\n%s",
festate->cursor_number, sql);
fsstate->cursor_number, sql);
/*
* We don't use a PG_TRY block here, so be careful not to throw error
......@@ -971,12 +1618,12 @@ create_cursor(ForeignScanState *node)
PQclear(res);
/* Mark the cursor as created, and show no tuples have been retrieved */
festate->cursor_exists = true;
festate->tuples = NULL;
festate->num_tuples = 0;
festate->next_tuple = 0;
festate->fetch_ct_2 = 0;
festate->eof_reached = false;
fsstate->cursor_exists = true;
fsstate->tuples = NULL;
fsstate->num_tuples = 0;
fsstate->next_tuple = 0;
fsstate->fetch_ct_2 = 0;
fsstate->eof_reached = false;
/* Clean up */
pfree(buf.data);
......@@ -988,7 +1635,7 @@ create_cursor(ForeignScanState *node)
static void
fetch_more_data(ForeignScanState *node)
{
PgFdwExecutionState *festate = (PgFdwExecutionState *) node->fdw_state;
PgFdwScanState *fsstate = (PgFdwScanState *) node->fdw_state;
PGresult *volatile res = NULL;
MemoryContext oldcontext;
......@@ -996,14 +1643,14 @@ fetch_more_data(ForeignScanState *node)
* We'll store the tuples in the batch_cxt. First, flush the previous
* batch.
*/
festate->tuples = NULL;
MemoryContextReset(festate->batch_cxt);
oldcontext = MemoryContextSwitchTo(festate->batch_cxt);
fsstate->tuples = NULL;
MemoryContextReset(fsstate->batch_cxt);
oldcontext = MemoryContextSwitchTo(fsstate->batch_cxt);
/* PGresult must be released before leaving this function. */
PG_TRY();
{
PGconn *conn = festate->conn;
PGconn *conn = fsstate->conn;
char sql[64];
int fetch_size;
int numrows;
......@@ -1013,36 +1660,36 @@ fetch_more_data(ForeignScanState *node)
fetch_size = 100;
snprintf(sql, sizeof(sql), "FETCH %d FROM c%u",
fetch_size, festate->cursor_number);
fetch_size, fsstate->cursor_number);
res = PQexec(conn, sql);
/* On error, report the original query, not the FETCH. */
if (PQresultStatus(res) != PGRES_TUPLES_OK)
pgfdw_report_error(ERROR, res, false,
strVal(list_nth(festate->fdw_private,
FdwPrivateSelectSql)));
strVal(list_nth(fsstate->fdw_private,
FdwScanPrivateSelectSql)));
/* Convert the data into HeapTuples */
numrows = PQntuples(res);
festate->tuples = (HeapTuple *) palloc0(numrows * sizeof(HeapTuple));
festate->num_tuples = numrows;
festate->next_tuple = 0;
fsstate->tuples = (HeapTuple *) palloc0(numrows * sizeof(HeapTuple));
fsstate->num_tuples = numrows;
fsstate->next_tuple = 0;
for (i = 0; i < numrows; i++)
{
festate->tuples[i] =
fsstate->tuples[i] =
make_tuple_from_result_row(res, i,
festate->rel,
festate->attinmeta,
festate->temp_cxt);
fsstate->rel,
fsstate->attinmeta,
fsstate->temp_cxt);
}
/* Update fetch_ct_2 */
if (festate->fetch_ct_2 < 2)
festate->fetch_ct_2++;
if (fsstate->fetch_ct_2 < 2)
fsstate->fetch_ct_2++;
/* Must be EOF if we didn't get as many tuples as we asked for. */
festate->eof_reached = (numrows < fetch_size);
fsstate->eof_reached = (numrows < fetch_size);
PQclear(res);
res = NULL;
......@@ -1079,6 +1726,136 @@ close_cursor(PGconn *conn, unsigned int cursor_number)
PQclear(res);
}
/*
* prepare_foreign_modify
* Establish a prepared statement for execution of INSERT/UPDATE/DELETE
*/
static void
prepare_foreign_modify(PgFdwModifyState *fmstate)
{
char prep_name[NAMEDATALEN];
char *p_name;
PGresult *res;
/* Construct name we'll use for the prepared statement. */
snprintf(prep_name, sizeof(prep_name), "pgsql_fdw_prep_%u",
GetPrepStmtNumber(fmstate->conn));
p_name = pstrdup(prep_name);
/*
* We intentionally do not specify parameter types here, but leave the
* remote server to derive them by default. This avoids possible problems
* with the remote server using different type OIDs than we do. All of
* the prepared statements we use in this module are simple enough that
* the remote server will make the right choices.
*
* We don't use a PG_TRY block here, so be careful not to throw error
* without releasing the PGresult.
*/
res = PQprepare(fmstate->conn,
p_name,
fmstate->query,
0,
NULL);
if (PQresultStatus(res) != PGRES_COMMAND_OK)
pgfdw_report_error(ERROR, res, true, fmstate->query);
PQclear(res);
/* This action shows that the prepare has been done. */
fmstate->p_name = p_name;
}
/*
* convert_prep_stmt_params
* Create array of text strings representing parameter values
*
* tupleid is ctid to send, or NULL if none
* slot is slot to get remaining parameters from, or NULL if none
*
* Data is constructed in temp_cxt; caller should reset that after use.
*/
static const char **
convert_prep_stmt_params(PgFdwModifyState *fmstate,
ItemPointer tupleid,
TupleTableSlot *slot)
{
const char **p_values;
int pindex = 0;
MemoryContext oldcontext;
oldcontext = MemoryContextSwitchTo(fmstate->temp_cxt);
p_values = (const char **) palloc(sizeof(char *) * fmstate->p_nums);
/* 1st parameter should be ctid, if it's in use */
if (tupleid != NULL)
{
p_values[pindex] = OutputFunctionCall(&fmstate->p_flinfo[pindex],
PointerGetDatum(tupleid));
pindex++;
}
/* get following parameters from slot */
if (slot != NULL)
{
ListCell *lc;
foreach(lc, fmstate->target_attrs)
{
int attnum = lfirst_int(lc);
Datum value;
bool isnull;
value = slot_getattr(slot, attnum, &isnull);
if (isnull)
p_values[pindex] = NULL;
else
p_values[pindex] = OutputFunctionCall(&fmstate->p_flinfo[pindex],
value);
pindex++;
}
}
Assert(pindex == fmstate->p_nums);
MemoryContextSwitchTo(oldcontext);
return p_values;
}
/*
* store_returning_result
* Store the result of a RETURNING clause
*
* On error, be sure to release the PGresult on the way out. Callers do not
* have PG_TRY blocks to ensure this happens.
*/
static void
store_returning_result(PgFdwModifyState *fmstate,
TupleTableSlot *slot, PGresult *res)
{
/* PGresult must be released before leaving this function. */
PG_TRY();
{
HeapTuple newtup;
newtup = make_tuple_from_result_row(res, 0,
fmstate->rel,
fmstate->attinmeta,
fmstate->temp_cxt);
/* tuple will be deleted when it is cleared from the slot */
ExecStoreTuple(newtup, slot, InvalidBuffer, true);
}
PG_CATCH();
{
if (res)
PQclear(res);
PG_RE_THROW();
}
PG_END_TRY();
}
/*
* postgresAnalyzeForeignTable
* Test whether analyzing this foreign table is supported
......@@ -1099,7 +1876,7 @@ postgresAnalyzeForeignTable(Relation relation,
*func = postgresAcquireSampleRowsFunc;
/*
* Now we have to get the number of pages. It's annoying that the ANALYZE
* Now we have to get the number of pages. It's annoying that the ANALYZE
* API requires us to return that now, because it forces some duplication
* of effort between this routine and postgresAcquireSampleRowsFunc. But
* it's probably not worth redefining that API at this point.
......@@ -1112,7 +1889,7 @@ postgresAnalyzeForeignTable(Relation relation,
table = GetForeignTable(RelationGetRelid(relation));
server = GetForeignServer(table->serverid);
user = GetUserMapping(relation->rd_rel->relowner, server->serverid);
conn = GetConnection(server, user);
conn = GetConnection(server, user, false);
/*
* Construct command to get page count for relation.
......@@ -1204,7 +1981,7 @@ postgresAcquireSampleRowsFunc(Relation relation, int elevel,
table = GetForeignTable(RelationGetRelid(relation));
server = GetForeignServer(table->serverid);
user = GetUserMapping(relation->rd_rel->relowner, server->serverid);
conn = GetConnection(server, user);
conn = GetConnection(server, user, false);
/*
* Construct cursor that retrieves whole rows from remote.
......@@ -1382,6 +2159,7 @@ make_tuple_from_result_row(PGresult *res,
Form_pg_attribute *attrs = tupdesc->attrs;
Datum *values;
bool *nulls;
ItemPointer ctid = NULL;
ConversionLocation errpos;
ErrorContextCallback errcallback;
MemoryContext oldcontext;
......@@ -1449,6 +2227,21 @@ make_tuple_from_result_row(PGresult *res,
j++;
}
/*
* Convert ctid if present. XXX we could stand to have a cleaner way of
* detecting whether ctid is included in the result.
*/
if (j < PQnfields(res))
{
char *valstr;
Datum datum;
valstr = PQgetvalue(res, row, j);
datum = DirectFunctionCall1(tidin, CStringGetDatum(valstr));
ctid = (ItemPointer) DatumGetPointer(datum);
j++;
}
/* Uninstall error context callback. */
error_context_stack = errcallback.previous;
......@@ -1463,6 +2256,9 @@ make_tuple_from_result_row(PGresult *res,
tuple = heap_form_tuple(tupdesc, values, nulls);
if (ctid)
tuple->t_self = *ctid;
/* Clean up */
MemoryContextReset(temp_context);
......
......@@ -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 &mdash; 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-&gt;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-&gt;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 &amp; 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-&gt;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 &mdash; 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 &mdash; 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);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment