diff --git a/contrib/postgres_fdw/connection.c b/contrib/postgres_fdw/connection.c new file mode 100644 index eab8b87..9ca7fbf *** a/contrib/postgres_fdw/connection.c --- b/contrib/postgres_fdw/connection.c *************** typedef struct ConnCacheEntry *** 43,49 **** Oid serverid; /* oid of foreign server */ Oid userid; /* oid of local user */ ! bool use_tx; /* true when using remote transaction */ int refs; /* reference counter */ PGconn *conn; /* foreign server connection */ } ConnCacheEntry; --- 43,49 ---- Oid serverid; /* oid of foreign server */ Oid userid; /* oid of local user */ ! int conntx; /* one of PGSQL_FDW_CONNTX_* */ int refs; /* reference counter */ PGconn *conn; /* foreign server connection */ } ConnCacheEntry; *************** cleanup_connection(ResourceReleasePhase *** 65,70 **** --- 65,72 ---- static PGconn *connect_pg_server(ForeignServer *server, UserMapping *user); static void begin_remote_tx(PGconn *conn); static void abort_remote_tx(PGconn *conn); + static void commit_remote_tx(PGconn *conn); + static void deallocate_remote_prepare(PGconn *conn); /* * Get a PGconn which can be used to execute foreign query on the remote *************** static void abort_remote_tx(PGconn *conn *** 80,86 **** * FDW object to invalidate already established connections. */ PGconn * ! GetConnection(ForeignServer *server, UserMapping *user, bool use_tx) { bool found; ConnCacheEntry *entry; --- 82,88 ---- * FDW object to invalidate already established connections. */ PGconn * ! GetConnection(ForeignServer *server, UserMapping *user, int conntx) { bool found; ConnCacheEntry *entry; *************** GetConnection(ForeignServer *server, Use *** 126,132 **** entry = hash_search(ConnectionHash, &key, HASH_ENTER, &found); if (!found) { ! entry->use_tx = false; entry->refs = 0; entry->conn = NULL; } --- 128,134 ---- entry = hash_search(ConnectionHash, &key, HASH_ENTER, &found); if (!found) { ! entry->conntx = PGSQL_FDW_CONNTX_NONE; entry->refs = 0; entry->conn = NULL; } *************** GetConnection(ForeignServer *server, Use *** 162,168 **** { /* Clear connection cache entry on error case. */ PQfinish(entry->conn); ! entry->use_tx = false; entry->refs = 0; entry->conn = NULL; PG_RE_THROW(); --- 164,170 ---- { /* Clear connection cache entry on error case. */ PQfinish(entry->conn); ! entry->conntx = PGSQL_FDW_CONNTX_NONE; entry->refs = 0; entry->conn = NULL; PG_RE_THROW(); *************** GetConnection(ForeignServer *server, Use *** 182,191 **** * are in. We need to remember whether this connection uses remote * transaction to abort it when this connection is released completely. */ ! if (use_tx && !entry->use_tx) { ! begin_remote_tx(entry->conn); ! entry->use_tx = use_tx; } return entry->conn; --- 184,194 ---- * are in. We need to remember whether this connection uses remote * transaction to abort it when this connection is released completely. */ ! if (conntx > entry->conntx) { ! if (entry->conntx == PGSQL_FDW_CONNTX_NONE) ! begin_remote_tx(entry->conn); ! entry->conntx = conntx; } return entry->conn; *************** abort_remote_tx(PGconn *conn) *** 355,366 **** PQclear(res); } /* * Mark the connection as "unused", and close it if the caller was the last * user of the connection. */ void ! ReleaseConnection(PGconn *conn) { HASH_SEQ_STATUS scan; ConnCacheEntry *entry; --- 358,402 ---- PQclear(res); } + static void + commit_remote_tx(PGconn *conn) + { + PGresult *res; + + elog(DEBUG3, "committing remote transaction"); + + res = PQexec(conn, "COMMIT TRANSACTION"); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + elog(ERROR, "could not commit transaction: %s", PQerrorMessage(conn)); + } + PQclear(res); + } + + static void + deallocate_remote_prepare(PGconn *conn) + { + PGresult *res; + + elog(DEBUG3, "deallocating remote prepares"); + + res = PQexec(conn, "DEALLOCATE PREPARE ALL"); + if (PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + elog(ERROR, "could not deallocate prepared statement: %s", + PQerrorMessage(conn)); + } + PQclear(res); + } + /* * Mark the connection as "unused", and close it if the caller was the last * user of the connection. */ void ! ReleaseConnection(PGconn *conn, bool is_abort) { HASH_SEQ_STATUS scan; ConnCacheEntry *entry; *************** ReleaseConnection(PGconn *conn) *** 412,418 **** PQtransactionStatus(conn) == PQTRANS_INERROR ? "INERROR" : "UNKNOWN"); PQfinish(conn); ! entry->use_tx = false; entry->refs = 0; entry->conn = NULL; return; --- 448,454 ---- PQtransactionStatus(conn) == PQTRANS_INERROR ? "INERROR" : "UNKNOWN"); PQfinish(conn); ! entry->conntx = PGSQL_FDW_CONNTX_NONE; entry->refs = 0; entry->conn = NULL; return; *************** ReleaseConnection(PGconn *conn) *** 430,439 **** * If this connection uses remote transaction and there is no user other * than the caller, abort the remote transaction and forget about it. */ ! if (entry->use_tx && entry->refs == 0) { ! abort_remote_tx(conn); ! entry->use_tx = false; } } --- 466,481 ---- * If this connection uses remote transaction and there is no user other * than the caller, abort the remote transaction and forget about it. */ ! if (entry->conntx > PGSQL_FDW_CONNTX_NONE && entry->refs == 0) { ! if (entry->conntx > PGSQL_FDW_CONNTX_READ_ONLY) ! deallocate_remote_prepare(conn); ! if (is_abort || entry->conntx == PGSQL_FDW_CONNTX_READ_ONLY) ! abort_remote_tx(conn); ! else ! commit_remote_tx(conn); ! ! entry->conntx = PGSQL_FDW_CONNTX_NONE; } } *************** cleanup_connection(ResourceReleasePhase *** 485,491 **** elog(DEBUG3, "discard postgres_fdw connection %p due to resowner cleanup", entry->conn); PQfinish(entry->conn); ! entry->use_tx = false; entry->refs = 0; entry->conn = NULL; } --- 527,533 ---- elog(DEBUG3, "discard postgres_fdw connection %p due to resowner cleanup", entry->conn); PQfinish(entry->conn); ! entry->conntx = PGSQL_FDW_CONNTX_NONE; entry->refs = 0; entry->conn = NULL; } *************** postgres_fdw_disconnect(PG_FUNCTION_ARGS *** 597,603 **** /* Discard cached connection, and clear reference counter. */ PQfinish(entry->conn); ! entry->use_tx = false; entry->refs = 0; entry->conn = NULL; --- 639,645 ---- /* Discard cached connection, and clear reference counter. */ PQfinish(entry->conn); ! entry->conntx = PGSQL_FDW_CONNTX_NONE; entry->refs = 0; entry->conn = NULL; diff --git a/contrib/postgres_fdw/connection.h b/contrib/postgres_fdw/connection.h new file mode 100644 index 4c9d850..f97cc8a *** a/contrib/postgres_fdw/connection.h --- b/contrib/postgres_fdw/connection.h *************** *** 16,25 **** #include "foreign/foreign.h" #include "libpq-fe.h" /* * Connection management */ ! PGconn *GetConnection(ForeignServer *server, UserMapping *user, bool use_tx); ! void ReleaseConnection(PGconn *conn); #endif /* CONNECTION_H */ --- 16,29 ---- #include "foreign/foreign.h" #include "libpq-fe.h" + #define PGSQL_FDW_CONNTX_NONE 0 + #define PGSQL_FDW_CONNTX_READ_ONLY 1 + #define PGSQL_FDW_CONNTX_READ_WRITE 2 + /* * Connection management */ ! PGconn *GetConnection(ForeignServer *server, UserMapping *user, int conntx); ! void ReleaseConnection(PGconn *conn, bool is_abort); #endif /* CONNECTION_H */ diff --git a/contrib/postgres_fdw/deparse.c b/contrib/postgres_fdw/deparse.c new file mode 100644 index 69e6a3e..9e09429 *** a/contrib/postgres_fdw/deparse.c --- b/contrib/postgres_fdw/deparse.c *************** *** 12,17 **** --- 12,18 ---- */ #include "postgres.h" + #include "access/heapam.h" #include "access/htup_details.h" #include "access/transam.h" #include "catalog/pg_class.h" *************** void *** 86,94 **** deparseSimpleSql(StringInfo buf, PlannerInfo *root, RelOptInfo *baserel, ! List *local_conds) { RangeTblEntry *rte; ListCell *lc; StringInfoData foreign_relname; bool first; --- 87,97 ---- deparseSimpleSql(StringInfo buf, PlannerInfo *root, RelOptInfo *baserel, ! List *local_conds, ! AttrNumber anum_rowid) { RangeTblEntry *rte; + Relation rel; ListCell *lc; StringInfoData foreign_relname; bool first; *************** deparseSimpleSql(StringInfo buf, *** 125,130 **** --- 128,151 ---- } /* + * XXX - When this foreign table is target relation and RETURNING + * clause reference some column, we have to mark these columns as + * in-use. It is needed to support DELETE command, because INSERT + * and UPDATE implicitly add references to all the regular columns + * on baserel->reltargetlist. + */ + if (root->parse->resultRelation == baserel->relid && + root->parse->returningList) + { + List *attrs; + + attrs = pull_var_clause((Node *) root->parse->returningList, + PVC_RECURSE_AGGREGATES, + PVC_RECURSE_PLACEHOLDERS); + attr_used = list_union(attr_used, attrs); + } + + /* * deparse SELECT clause * * List attributes which are in either target list or local restriction. *************** deparseSimpleSql(StringInfo buf, *** 136,144 **** */ appendStringInfo(buf, "SELECT "); rte = root->simple_rte_array[baserel->relid]; attr_used = list_union(attr_used, baserel->reltargetlist); first = true; ! for (attr = 1; attr <= baserel->max_attr; attr++) { Var *var = NULL; ListCell *lc; --- 157,166 ---- */ appendStringInfo(buf, "SELECT "); rte = root->simple_rte_array[baserel->relid]; + rel = heap_open(rte->relid, NoLock); attr_used = list_union(attr_used, baserel->reltargetlist); first = true; ! for (attr = 1; attr <= RelationGetNumberOfAttributes(rel); attr++) { Var *var = NULL; ListCell *lc; *************** deparseSimpleSql(StringInfo buf, *** 167,172 **** --- 189,198 ---- else appendStringInfo(buf, "NULL"); } + if (anum_rowid != InvalidAttrNumber) + appendStringInfo(buf, "%sctid", (first ? "" : ",")); + + heap_close(rel, NoLock); appendStringInfoChar(buf, ' '); /* *************** deparseAnalyzeSql(StringInfo buf, Relati *** 283,288 **** --- 309,410 ---- } /* + * deparse remote INSERT statement + */ + void + deparseInsertSql(StringInfo buf, PlannerInfo *root, Index rtindex) + { + RangeTblEntry *rte = root->simple_rte_array[rtindex]; + Relation frel = heap_open(rte->relid, NoLock); + int i, j, nattrs = RelationGetNumberOfAttributes(frel); + bool is_first = true; + + appendStringInfo(buf, "INSERT INTO "); + deparseRelation(buf, rte); + appendStringInfo(buf, "("); + + for (i=0; i < nattrs; i++) + { + Form_pg_attribute attr = RelationGetDescr(frel)->attrs[i]; + Var var; + + if (attr->attisdropped) + continue; + + if (!is_first) + appendStringInfo(buf, ","); + + var.varno = rtindex; + var.varattno = attr->attnum; + deparseVar(buf, &var, root); + is_first = false; + } + appendStringInfo(buf, ") VALUES ("); + + for (i=0, j=1; i < nattrs; i++) + { + Form_pg_attribute attr = RelationGetDescr(frel)->attrs[i]; + + if (attr->attisdropped) + continue; + + appendStringInfo(buf, "%s$%d", (j == 1 ? "" : ","), j); + j++; + } + appendStringInfo(buf, ")"); + heap_close(frel, NoLock); + } + + /* + * deparse remote UPDATE statement + */ + void + deparseUpdateSql(StringInfo buf, PlannerInfo *root, Index rtindex) + { + RangeTblEntry *rte = root->simple_rte_array[rtindex]; + Relation frel = heap_open(rte->relid, NoLock); + int i, j, nattrs = RelationGetNumberOfAttributes(frel); + bool is_first = true; + + appendStringInfo(buf, "UPDATE "); + deparseRelation(buf, rte); + appendStringInfo(buf, " SET "); + + for (i=0, j=2; i < nattrs; i++) + { + Form_pg_attribute attr = RelationGetDescr(frel)->attrs[i]; + Var var; + + if (attr->attisdropped) + continue; + + if (!is_first) + appendStringInfo(buf, ","); + + var.varno = rtindex; + var.varattno = attr->attnum; + deparseVar(buf, &var, root); + appendStringInfo(buf, "=$%d", j++); + is_first = false; + } + appendStringInfo(buf, " WHERE ctid=$1"); + heap_close(frel, NoLock); + } + + /* + * deparse remote DELETE statement + */ + void + deparseDeleteSql(StringInfo buf, PlannerInfo *root, Index rtindex) + { + RangeTblEntry *rte = root->simple_rte_array[rtindex]; + + appendStringInfo(buf, "DELETE FROM "); + deparseRelation(buf, rte); + appendStringInfo(buf, " WHERE ctid = $1"); + } + + /* * Deparse given expression into buf. Actual string operation is delegated to * node-type-specific functions. * diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out new file mode 100644 index f81c727..db2c725 *** a/contrib/postgres_fdw/expected/postgres_fdw.out --- b/contrib/postgres_fdw/expected/postgres_fdw.out *************** SELECT srvname FROM postgres_fdw_connect *** 736,741 **** --- 736,1796 ---- (0 rows) -- =================================================================== + -- test for writable foreign table stuff (PoC stage now) + -- =================================================================== + 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) + + 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) + + 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) + + -- =================================================================== -- cleanup -- =================================================================== DROP OPERATOR === (int, int) CASCADE; diff --git a/contrib/postgres_fdw/postgres_fdw.c b/contrib/postgres_fdw/postgres_fdw.c new file mode 100644 index 6b870ab..787ee42 *** a/contrib/postgres_fdw/postgres_fdw.c --- b/contrib/postgres_fdw/postgres_fdw.c *************** typedef struct PostgresFdwPlanState { *** 59,64 **** --- 59,65 ---- List *param_conds; List *local_conds; int width; /* obtained by remote EXPLAIN */ + AttrNumber anum_rowid; /* Cached catalog information. */ ForeignTable *table; *************** typedef struct PostgresAnalyzeState *** 150,155 **** --- 151,170 ---- } PostgresAnalyzeState; /* + * Describes a state of modify request for a foreign table + */ + typedef struct PostgresFdwModifyState + { + PGconn *conn; + char *query; + char *p_name; + int p_nums; + Oid *p_types; + FmgrInfo *p_flinfo; + MemoryContext es_query_cxt; + } PostgresFdwModifyState; + + /* * SQL functions */ extern Datum postgres_fdw_handler(PG_FUNCTION_ARGS); *************** PG_FUNCTION_INFO_V1(postgres_fdw_handler *** 158,163 **** --- 173,183 ---- /* * FDW callback routines */ + static AttrNumber postgresGetForeignRelWidth(PlannerInfo *root, + RelOptInfo *baserel, + Relation foreignrel, + bool inhparent, + List *targetList); static void postgresGetForeignRelSize(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid); *************** static void postgresEndForeignScan(Forei *** 179,184 **** --- 199,221 ---- static bool postgresAnalyzeForeignTable(Relation relation, AcquireSampleRowsFunc *func, BlockNumber *totalpages); + static List *postgresPlanForeignModify(PlannerInfo *root, + ModifyTable *plan, + Index resultRelation, + Plan *subplan); + static void postgresBeginForeignModify(ModifyTableState *mtstate, + ResultRelInfo *resultRelInfo, + List *fdw_private, + Plan *subplan, + int eflags); + static int postgresExecForeignInsert(ResultRelInfo *resultRelInfo, + HeapTuple tuple); + static int postgresExecForeignDelete(ResultRelInfo *resultRelInfo, + Datum rowid); + static int postgresExecForeignUpdate(ResultRelInfo *resultRelInfo, + Datum rowid, + HeapTuple tuple); + static void postgresEndForeignModify(ResultRelInfo *resultRelInfo); /* * Helper functions *************** postgres_fdw_handler(PG_FUNCTION_ARGS) *** 231,236 **** --- 268,274 ---- FdwRoutine *routine = makeNode(FdwRoutine); /* Required handler functions. */ + routine->GetForeignRelWidth = postgresGetForeignRelWidth; routine->GetForeignRelSize = postgresGetForeignRelSize; routine->GetForeignPaths = postgresGetForeignPaths; routine->GetForeignPlan = postgresGetForeignPlan; *************** postgres_fdw_handler(PG_FUNCTION_ARGS) *** 239,244 **** --- 277,288 ---- routine->IterateForeignScan = postgresIterateForeignScan; routine->ReScanForeignScan = postgresReScanForeignScan; routine->EndForeignScan = postgresEndForeignScan; + routine->PlanForeignModify = postgresPlanForeignModify; + routine->BeginForeignModify = postgresBeginForeignModify; + routine->ExecForeignInsert = postgresExecForeignInsert; + routine->ExecForeignDelete = postgresExecForeignDelete; + routine->ExecForeignUpdate = postgresExecForeignUpdate; + routine->EndForeignModify = postgresEndForeignModify; /* Optional handler functions. */ routine->AnalyzeForeignTable = postgresAnalyzeForeignTable; *************** postgres_fdw_handler(PG_FUNCTION_ARGS) *** 247,252 **** --- 291,319 ---- } /* + * postgresGetForeignRelWidth + * Informs how many columns (including pseudo ones) are needed. + */ + static AttrNumber + postgresGetForeignRelWidth(PlannerInfo *root, + RelOptInfo *baserel, + Relation foreignrel, + bool inhparent, + List *targetList) + { + PostgresFdwPlanState *fpstate = palloc0(sizeof(PostgresFdwPlanState)); + + baserel->fdw_private = fpstate; + + /* does rowid pseudo-column is required? */ + fpstate->anum_rowid = get_pseudo_rowid_column(baserel, targetList); + if (fpstate->anum_rowid != InvalidAttrNumber) + return fpstate->anum_rowid; + + return RelationGetNumberOfAttributes(foreignrel); + } + + /* * postgresGetForeignRelSize * Estimate # of rows and width of the result of the scan * *************** postgresGetForeignRelSize(PlannerInfo *r *** 283,289 **** * We use PostgresFdwPlanState to pass various information to subsequent * functions. */ ! fpstate = palloc0(sizeof(PostgresFdwPlanState)); initStringInfo(&fpstate->sql); sql = &fpstate->sql; --- 350,356 ---- * We use PostgresFdwPlanState to pass various information to subsequent * functions. */ ! fpstate = baserel->fdw_private; initStringInfo(&fpstate->sql); sql = &fpstate->sql; *************** postgresGetForeignRelSize(PlannerInfo *r *** 320,329 **** */ classifyConditions(root, baserel, &remote_conds, ¶m_conds, &local_conds); ! deparseSimpleSql(sql, root, baserel, local_conds); if (list_length(remote_conds) > 0) appendWhereClause(sql, true, remote_conds, root); - elog(DEBUG3, "Query SQL: %s", sql->data); /* * If the table or the server is configured to use remote EXPLAIN, connect --- 387,395 ---- */ classifyConditions(root, baserel, &remote_conds, ¶m_conds, &local_conds); ! deparseSimpleSql(sql, root, baserel, local_conds, fpstate->anum_rowid); if (list_length(remote_conds) > 0) appendWhereClause(sql, true, remote_conds, root); /* * If the table or the server is configured to use remote EXPLAIN, connect *************** postgresGetForeignRelSize(PlannerInfo *r *** 337,346 **** PGconn *conn; user = GetUserMapping(GetOuterUserId(), server->serverid); ! conn = GetConnection(server, user, false); get_remote_estimate(sql->data, conn, &rows, &width, &startup_cost, &total_cost); ! ReleaseConnection(conn); /* * Estimate selectivity of conditions which are not used in remote --- 403,412 ---- PGconn *conn; user = GetUserMapping(GetOuterUserId(), server->serverid); ! conn = GetConnection(server, user, PGSQL_FDW_CONNTX_NONE); get_remote_estimate(sql->data, conn, &rows, &width, &startup_cost, &total_cost); ! ReleaseConnection(conn, false); /* * Estimate selectivity of conditions which are not used in remote *************** postgresGetForeignRelSize(PlannerInfo *r *** 391,397 **** fpstate->width = width; fpstate->table = table; fpstate->server = server; - baserel->fdw_private = (void *) fpstate; } /* --- 457,462 ---- *************** postgresBeginForeignScan(ForeignScanStat *** 592,598 **** table = GetForeignTable(relid); server = GetForeignServer(table->serverid); user = GetUserMapping(GetOuterUserId(), server->serverid); ! conn = GetConnection(server, user, true); festate->conn = conn; /* Result will be filled in first Iterate call. */ --- 657,663 ---- table = GetForeignTable(relid); server = GetForeignServer(table->serverid); user = GetUserMapping(GetOuterUserId(), server->serverid); ! conn = GetConnection(server, user, PGSQL_FDW_CONNTX_READ_ONLY); festate->conn = conn; /* Result will be filled in first Iterate call. */ *************** postgresEndForeignScan(ForeignScanState *** 724,730 **** * end of the scan to make the lifespan of remote transaction same as the * local query. */ ! ReleaseConnection(festate->conn); festate->conn = NULL; /* Discard fetch results */ --- 789,795 ---- * end of the scan to make the lifespan of remote transaction same as the * local query. */ ! ReleaseConnection(festate->conn, false); festate->conn = NULL; /* Discard fetch results */ *************** get_remote_estimate(const char *sql, PGc *** 790,796 **** PQclear(res); /* Release connection and let connection manager cleanup. */ ! ReleaseConnection(conn); PG_RE_THROW(); } --- 855,861 ---- PQclear(res); /* Release connection and let connection manager cleanup. */ ! ReleaseConnection(conn, true); PG_RE_THROW(); } *************** execute_query(ForeignScanState *node) *** 947,953 **** PQclear(res); /* Release connection and let connection manager cleanup. */ ! ReleaseConnection(conn); /* propagate error */ PG_RE_THROW(); --- 1012,1018 ---- PQclear(res); /* Release connection and let connection manager cleanup. */ ! ReleaseConnection(conn, true); /* propagate error */ PG_RE_THROW(); *************** postgres_fdw_error_callback(void *arg) *** 1105,1110 **** --- 1170,1177 ---- relname = get_rel_name(errpos->relid); colname = get_attname(errpos->relid, errpos->cur_attno); + if (!colname) + colname = "pseudo-column"; errcontext("column %s of foreign table %s", quote_identifier(colname), quote_identifier(relname)); } *************** postgresAcquireSampleRowsFunc(Relation r *** 1172,1178 **** table = GetForeignTable(relation->rd_id); server = GetForeignServer(table->serverid); user = GetUserMapping(GetOuterUserId(), server->serverid); ! conn = GetConnection(server, user, true); /* * Acquire sample rows from the result set. --- 1239,1245 ---- table = GetForeignTable(relation->rd_id); server = GetForeignServer(table->serverid); user = GetUserMapping(GetOuterUserId(), server->serverid); ! conn = GetConnection(server, user, PGSQL_FDW_CONNTX_READ_ONLY); /* * Acquire sample rows from the result set. *************** postgresAcquireSampleRowsFunc(Relation r *** 1239,1251 **** PQclear(res); /* Release connection and let connection manager cleanup. */ ! ReleaseConnection(conn); PG_RE_THROW(); } PG_END_TRY(); ! ReleaseConnection(conn); /* We assume that we have no dead tuple. */ *totaldeadrows = 0.0; --- 1306,1318 ---- PQclear(res); /* Release connection and let connection manager cleanup. */ ! ReleaseConnection(conn, true); PG_RE_THROW(); } PG_END_TRY(); ! ReleaseConnection(conn, false); /* We assume that we have no dead tuple. */ *totaldeadrows = 0.0; *************** analyze_row_processor(PGresult *res, Pos *** 1429,1431 **** --- 1496,1813 ---- return; } + + static List * + postgresPlanForeignModify(PlannerInfo *root, + ModifyTable *plan, + Index resultRelation, + Plan *subplan) + { + CmdType operation = plan->operation; + StringInfoData sql; + + initStringInfo(&sql); + + /* + * XXX - In case of UPDATE or DELETE commands are quite "simple", + * we will be able to execute raw UPDATE or DELETE statement at + * the stage of scan, instead of combination SELECT ... FOR UPDATE + * and either of UPDATE or DELETE commands. + * It should be an idea of optimization in the future version. + * + * XXX - FOR UPDATE should be appended on the remote query of scan + * stage to avoid unexpected concurrent update on the target rows. + */ + if (operation == CMD_UPDATE || operation == CMD_DELETE) + { + ForeignScan *fscan; + Value *select_sql; + + fscan = lookup_foreign_scan_plan(subplan, resultRelation); + if (!fscan) + elog(ERROR, "no underlying scan plan found in subplan tree"); + + select_sql = list_nth(fscan->fdw_private, + FdwPrivateSelectSql); + appendStringInfo(&sql, "%s FOR UPDATE", strVal(select_sql)); + strVal(select_sql) = pstrdup(sql.data); + + resetStringInfo(&sql); + } + + switch (operation) + { + case CMD_INSERT: + deparseInsertSql(&sql, root, resultRelation); + elog(DEBUG3, "Remote INSERT query: %s", sql.data); + break; + case CMD_UPDATE: + deparseUpdateSql(&sql, root, resultRelation); + elog(DEBUG3, "Remote UPDATE query: %s", sql.data); + break; + case CMD_DELETE: + deparseDeleteSql(&sql, root, resultRelation); + elog(DEBUG3, "Remote DELETE query: %s", sql.data); + break; + default: + elog(ERROR, "unexpected operation: %d", (int) operation); + } + return list_make1(makeString(sql.data)); + } + + static void + postgresBeginForeignModify(ModifyTableState *mtstate, + ResultRelInfo *resultRelInfo, + List *fdw_private, + Plan *subplan, + int eflags) + { + PostgresFdwModifyState *fmstate; + CmdType operation = mtstate->operation; + Relation frel = resultRelInfo->ri_RelationDesc; + AttrNumber n_params = RelationGetNumberOfAttributes(frel) + 1; + ForeignTable *ftable; + ForeignServer *fserver; + UserMapping *fuser; + Oid out_func_oid; + bool isvarlena; + + /* + * Construct PostgresFdwExecutionState + */ + fmstate = palloc0(sizeof(PostgresFdwExecutionState)); + + ftable = GetForeignTable(RelationGetRelid(frel)); + fserver = GetForeignServer(ftable->serverid); + fuser = GetUserMapping(GetOuterUserId(), fserver->serverid); + + fmstate->query = strVal(linitial(fdw_private)); + fmstate->conn = GetConnection(fserver, fuser, + PGSQL_FDW_CONNTX_READ_WRITE); + fmstate->p_name = NULL; + fmstate->p_types = palloc0(sizeof(Oid) * n_params); + fmstate->p_flinfo = palloc0(sizeof(FmgrInfo) * n_params); + + /* 1st parameter should be ctid on UPDATE or DELETE */ + if (operation == CMD_UPDATE || operation == CMD_DELETE) + { + fmstate->p_types[fmstate->p_nums] = TIDOID; + getTypeOutputInfo(TIDOID, &out_func_oid, &isvarlena); + fmgr_info(out_func_oid, &fmstate->p_flinfo[fmstate->p_nums]); + fmstate->p_nums++; + } + /* following parameters should be regular columns */ + if (operation == CMD_UPDATE || operation == CMD_INSERT) + { + AttrNumber index; + + for (index=0; index < RelationGetNumberOfAttributes(frel); index++) + { + Form_pg_attribute attr = RelationGetDescr(frel)->attrs[index]; + + if (attr->attisdropped) + continue; + + fmstate->p_types[fmstate->p_nums] = attr->atttypid; + getTypeOutputInfo(attr->atttypid, &out_func_oid, &isvarlena); + fmgr_info(out_func_oid, &fmstate->p_flinfo[fmstate->p_nums]); + fmstate->p_nums++; + } + } + Assert(fmstate->p_nums <= n_params); + fmstate->es_query_cxt = mtstate->ps.state->es_query_cxt; + resultRelInfo->ri_fdw_state = fmstate; + } + + static void + prepare_foreign_modify(PostgresFdwModifyState *fmstate) + { + static int prep_id = 1; + char prep_name[NAMEDATALEN]; + PGresult *res; + + snprintf(prep_name, sizeof(prep_name), + "pgsql_fdw_prep_%08x", prep_id++); + + res = PQprepare(fmstate->conn, + prep_name, + fmstate->query, + fmstate->p_nums, + fmstate->p_types); + if (!res || PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + elog(ERROR, "could not prepare statement (%s): %s", + fmstate->query, PQerrorMessage(fmstate->conn)); + } + PQclear(res); + + fmstate->p_name = MemoryContextStrdup(fmstate->es_query_cxt, prep_name); + } + + static int + setup_exec_prepared(ResultRelInfo *resultRelInfo, + const char *rowid, HeapTuple tuple, + const char *p_values[], int p_lengths[]) + { + PostgresFdwModifyState *fmstate = resultRelInfo->ri_fdw_state; + Relation frel = resultRelInfo->ri_RelationDesc; + TupleDesc tupdesc = RelationGetDescr(frel); + int i, p_idx = 0; + + /* 1st parameter should be ctid */ + if (rowid) + { + p_values[p_idx] = rowid; + p_lengths[p_idx] = strlen(rowid) + 1; + p_idx++; + } + + /* following parameters are as TupleDesc */ + if (HeapTupleIsValid(tuple)) + { + for (i = 0; i < tupdesc->natts; i++) + { + Form_pg_attribute attr = tupdesc->attrs[i]; + Datum value; + bool isnull; + + if (attr->attisdropped) + continue; + + value = heap_getattr(tuple, attr->attnum, tupdesc, &isnull); + if (isnull) + { + p_values[p_idx] = NULL; + p_lengths[p_idx] = 0; + } + else + { + p_values[p_idx] = + OutputFunctionCall(&fmstate->p_flinfo[p_idx], value); + p_lengths[p_idx] = strlen(p_values[p_idx]) + 1; + } + p_idx++; + } + } + return p_idx; + } + + static int + postgresExecForeignInsert(ResultRelInfo *resultRelInfo, + HeapTuple tuple) + { + PostgresFdwModifyState *fmstate = resultRelInfo->ri_fdw_state; + const char **p_values = alloca(sizeof(char *) * fmstate->p_nums); + int *p_lengths = alloca(sizeof(int) * fmstate->p_nums); + AttrNumber nattrs; + PGresult *res; + int n_rows; + + if (!fmstate->p_name) + prepare_foreign_modify(fmstate); + + nattrs = setup_exec_prepared(resultRelInfo, + NULL, tuple, + p_values, p_lengths); + Assert(fmstate->p_nums == nattrs); + + res = PQexecPrepared(fmstate->conn, + fmstate->p_name, + nattrs, + p_values, + p_lengths, + NULL, 0); + if (!res || PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + elog(ERROR, "could not execute prepared statement (%s): %s", + fmstate->query, PQerrorMessage(fmstate->conn)); + } + n_rows = atoi(PQcmdTuples(res)); + PQclear(res); + + return n_rows; + } + + static int + postgresExecForeignDelete(ResultRelInfo *resultRelInfo, Datum rowid) + { + PostgresFdwModifyState *fmstate = resultRelInfo->ri_fdw_state; + const char *p_values[1]; + int p_lengths[1]; + AttrNumber nattrs; + PGresult *res; + int n_rows; + + if (!fmstate->p_name) + prepare_foreign_modify(fmstate); + + nattrs = setup_exec_prepared(resultRelInfo, + DatumGetCString(rowid), NULL, + p_values, p_lengths); + Assert(fmstate->p_nums == nattrs); + + res = PQexecPrepared(fmstate->conn, + fmstate->p_name, + nattrs, + p_values, + p_lengths, + NULL, 0); + if (!res || PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + elog(ERROR, "could not execute prepared statement (%s): %s", + fmstate->query, PQerrorMessage(fmstate->conn)); + } + n_rows = atoi(PQcmdTuples(res)); + PQclear(res); + + return n_rows; + } + + static int + postgresExecForeignUpdate(ResultRelInfo *resultRelInfo, + Datum rowid, HeapTuple tuple) + { + PostgresFdwModifyState *fmstate = resultRelInfo->ri_fdw_state; + const char **p_values = alloca(sizeof(char *) * (fmstate->p_nums + 1)); + int *p_lengths = alloca(sizeof(int) * (fmstate->p_nums + 1)); + AttrNumber nattrs; + PGresult *res; + int n_rows; + + if (!fmstate->p_name) + prepare_foreign_modify(fmstate); + + nattrs = setup_exec_prepared(resultRelInfo, + DatumGetCString(rowid), tuple, + p_values, p_lengths); + Assert(fmstate->p_nums == nattrs); + + res = PQexecPrepared(fmstate->conn, + fmstate->p_name, + nattrs, + p_values, + p_lengths, + NULL, 0); + if (!res || PQresultStatus(res) != PGRES_COMMAND_OK) + { + PQclear(res); + elog(ERROR, "could not execute prepared statement (%s): %s", + fmstate->query, PQerrorMessage(fmstate->conn)); + } + n_rows = atoi(PQcmdTuples(res)); + PQclear(res); + + return n_rows; + } + + static void + postgresEndForeignModify(ResultRelInfo *resultRelInfo) + { + PostgresFdwModifyState *fmstate = resultRelInfo->ri_fdw_state; + + ReleaseConnection(fmstate->conn, false); + fmstate->conn = NULL; + } diff --git a/contrib/postgres_fdw/postgres_fdw.h b/contrib/postgres_fdw/postgres_fdw.h new file mode 100644 index b5cefb8..3756a0d *** a/contrib/postgres_fdw/postgres_fdw.h --- b/contrib/postgres_fdw/postgres_fdw.h *************** int GetFetchCountOption(ForeignTable *ta *** 30,36 **** void deparseSimpleSql(StringInfo buf, PlannerInfo *root, RelOptInfo *baserel, ! List *local_conds); void appendWhereClause(StringInfo buf, bool has_where, List *exprs, --- 30,37 ---- void deparseSimpleSql(StringInfo buf, PlannerInfo *root, RelOptInfo *baserel, ! List *local_conds, ! AttrNumber anum_rowid); void appendWhereClause(StringInfo buf, bool has_where, List *exprs, *************** void classifyConditions(PlannerInfo *roo *** 41,45 **** --- 42,49 ---- List **param_conds, List **local_conds); void deparseAnalyzeSql(StringInfo buf, Relation rel); + void deparseInsertSql(StringInfo buf, PlannerInfo *root, Index rtindex); + void deparseUpdateSql(StringInfo buf, PlannerInfo *root, Index rtindex); + void deparseDeleteSql(StringInfo buf, PlannerInfo *root, Index rtindex); #endif /* POSTGRESQL_FDW_H */ diff --git a/contrib/postgres_fdw/sql/postgres_fdw.sql b/contrib/postgres_fdw/sql/postgres_fdw.sql new file mode 100644 index 7845e70..70b09b4 *** a/contrib/postgres_fdw/sql/postgres_fdw.sql --- b/contrib/postgres_fdw/sql/postgres_fdw.sql *************** ERROR OUT; -- ERROR *** 300,305 **** --- 300,318 ---- SELECT srvname FROM postgres_fdw_connections; -- =================================================================== + -- test for writable foreign table stuff (PoC stage now) + -- =================================================================== + 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 *; + 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 *; + 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; + + -- =================================================================== -- cleanup -- =================================================================== DROP OPERATOR === (int, int) CASCADE; diff --git a/doc/src/sgml/ddl.sgml b/doc/src/sgml/ddl.sgml new file mode 100644 index d6e5d64..30f9c6b *** a/doc/src/sgml/ddl.sgml --- b/doc/src/sgml/ddl.sgml *************** ANALYZE measurement; *** 3066,3076 **** user mapping, which can provide additional options based on the current PostgreSQL role. - - - Currently, foreign tables are read-only. This limitation may be fixed - in a future release. - --- 3066,3071 ---- diff --git a/doc/src/sgml/fdwhandler.sgml b/doc/src/sgml/fdwhandler.sgml new file mode 100644 index 2d604ed..cbf3181 *** a/doc/src/sgml/fdwhandler.sgml --- b/doc/src/sgml/fdwhandler.sgml *************** *** 89,94 **** --- 89,142 ---- + AttrNumber + GetForeignRelWidth(PlannerInfo *root, + RelOptInfo *baserel, + Relation foreignrel, + bool inhparent, + List *targetList); + + Obtain the width of the result set to be fetched during a foreign table scan. + This is an optional handler, and called before GetForeignRelSize + for a query involving a foreign table + (during the construction of RelOptInfo). + root is the planner's global information about the query, + baserel is the planner's information being constructed for + this query, and foreignrel is a Relation + descriptor of the foreign table. + inhparent is a boolean to show whether the relation is + an inheritance parent, even though foreign tables do not support table + inheritance right now. targetList is the list of + TargetEntry to be returned from the (sub-)query + that is currently in focus. + + + + The result value of this function will be assigned to + baserel->max_attr, that means it is the expected number + of columns being fetched during the foreign table scan. + It should not be smaller than the number of regular columns in the definition + of this foreign table. You can only return a number greater than his value to + acquire slots for some additional attributes, which are called + pseudo-columns. + A typical usage of a pseudo-column is to carry an identifier of + a particular remote row to be updated or deleted from the scanning stage + to the modifying stage when the foreign table is the target of + a data-modifying SQL statement. + You can return the result of the helper function + get_pseudo_rowid_column if this "rowid" + pseudo-column is the only one you need. + + + + In addition to that, pseudo-columns can be used to off-load the burden of + complex calculations to foreign computing resources by replacing an + expression with a reference to its result, which is calculated on the + remote side rather than locally. + + + + void GetForeignRelSize (PlannerInfo *root, RelOptInfo *baserel, *************** GetForeignRelSize (PlannerInfo *root, *** 96,102 **** Obtain relation size estimates for a foreign table. This is called ! at the beginning of planning for a query involving a foreign table. root is the planner's global information about the query; baserel is the planner's information about this table; and foreigntableid is the pg_class OID of the --- 144,151 ---- Obtain relation size estimates for a foreign table. This is called ! for a query involving a foreign table at the beginning of planning ! or right after GetForeignRelWidth, if that callback is configured. root is the planner's global information about the query; baserel is the planner's information about this table; and foreigntableid is the pg_class OID of the *************** AcquireSampleRowsFunc (Relation relation *** 315,320 **** --- 364,479 ---- + If a FDW supports writable foreign tables, it must implement at least both + BeginForeignModify and EndForeignModify. + In addition, some or all of ExecForeignInsert, + ExecForeignUpdate and ExecForeignDelete need + to be implement according to the capability of the FDW. + + + + + List * + PlanForeignModify(PlannerInfo *root, + ModifyTable *plan, + Index resultRelation, + Plan *subplan); + + It allows FDW drivers to construct private information relevant to + the modification of the foreign table. This private information must have + the form of a List *, which will be delivered as + third argument to BeginForeignModify during the execution stage. + + + + root is the planner's global information about the query. + plan is the master plan to modify the result relation according + to its command type. Please consider that ModifyTable + may have multiple result relations in a future revision, even though + currently there is no support for table inheritance on foreign tables. + resultRelation is an index for the result relation in the + range table entries, and subplan is the relevant scan plan; + that should be a ForeignScan for UPDATE or + DELETE, so the driver can access the private information of + the scan stage using this argument. + + + + + void + BeginForeignModify (ModifyTableState *mtstate, + ResultRelInfo *resultRelInfo, + List *fdw_private, + Plan *subplan, + int eflags); + + It is invoked at beginning of foreign table modification, during + executor startup. This routine should perform any initialization + needed prior to the actual table modifications, but not start + modifying the actual tuples. (That should be done during each call of + ExecForeignInsert, ExecForeignUpdate or + ExecForeignDelete.) + + + + The ri_fdw_state field of ResultRelInfo + is reserved to store any private information relevant to this foreign table, + so it should be initialized to contain the per-scan state. + Please consider that ModifyTableState may have multiple + result relations in a future revision, even though currently there is no + support for table inheritance on foreign tables. resultRelInfo + is the master information connected to this foreign table. + fdw_private is private information constructed in + PlanForeignModify, and subplan is the relevant + scan plan of this table modification. + + + + + int + ExecForeignInsert (ResultRelInfo *resultRelInfo, + HeapTuple tuple); + + Insert the given tuple into backing storage on behalf of the foreign table. + The supplied tuple is already formed according to the definition of + the relation, thus all the pseudo-columns are already filtered out. + It can return the number of rows being inserted, usually 1. + + + + + int + ExecForeignDelete (ResultRelInfo *resultRelInfo, + Datum rowid); + + Delete the tuple being identified with rowid from the backing + storage on behalf of the foreign table. + It can return the number of rows being deleted, usually 1. + + + + + int + ExecForeignUpdate (ResultRelInfo *resultRelInfo, + Datum rowid, + HeapTuple tuple); + + Update the tuple being identified with rowid in the backing + storage on behalf of the foreign table with the given tuple. + It can return the number of rows being updated, usually 1. + + + + + void + EndForeignModify (ResultRelInfo *resultRelInfo); + + End the modification and release resources. It is normally not important + to release palloc'd memory, but for example open files and connections + to remote servers should be cleaned up. + + + The FdwRoutine struct type is declared in src/include/foreign/fdwapi.h, which see for additional details. diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c new file mode 100644 index dbd3755..ccc1f3a *** a/src/backend/executor/execMain.c --- b/src/backend/executor/execMain.c *************** *** 44,49 **** --- 44,50 ---- #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" *************** void *** 934,939 **** --- 935,941 ---- CheckValidResultRel(Relation resultRel, CmdType operation) { TriggerDesc *trigDesc = resultRel->trigdesc; + FdwRoutine *fdwroutine; switch (resultRel->rd_rel->relkind) { *************** CheckValidResultRel(Relation resultRel, *** 985,994 **** } break; case RELKIND_FOREIGN_TABLE: ! ereport(ERROR, ! (errcode(ERRCODE_WRONG_OBJECT_TYPE), ! errmsg("cannot change foreign table \"%s\"", ! RelationGetRelationName(resultRel)))); break; default: ereport(ERROR, --- 987,1020 ---- } break; case RELKIND_FOREIGN_TABLE: ! fdwroutine = GetFdwRoutineByRelId(RelationGetRelid(resultRel)); ! switch (operation) ! { ! case CMD_INSERT: ! if (!fdwroutine || !fdwroutine->ExecForeignInsert) ! ereport(ERROR, ! (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), ! errmsg("cannot insert into foreign table \"%s\"", ! RelationGetRelationName(resultRel)))); ! break; ! case CMD_UPDATE: ! if (!fdwroutine || !fdwroutine->ExecForeignUpdate) ! ereport(ERROR, ! (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), ! errmsg("cannot update foreign table \"%s\"", ! RelationGetRelationName(resultRel)))); ! break; ! case CMD_DELETE: ! if (!fdwroutine || !fdwroutine->ExecForeignDelete) ! ereport(ERROR, ! (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), ! errmsg("cannot delete from foreign table \"%s\"", ! RelationGetRelationName(resultRel)))); ! break; ! default: ! elog(ERROR, "unrecognized CmdType: %d", (int) operation); ! break; ! } break; default: ereport(ERROR, diff --git a/src/backend/executor/nodeForeignscan.c b/src/backend/executor/nodeForeignscan.c new file mode 100644 index 9204859..1ee626f *** a/src/backend/executor/nodeForeignscan.c --- b/src/backend/executor/nodeForeignscan.c *************** *** 25,30 **** --- 25,31 ---- #include "executor/executor.h" #include "executor/nodeForeignscan.h" #include "foreign/fdwapi.h" + #include "nodes/nodeFuncs.h" #include "utils/rel.h" static TupleTableSlot *ForeignNext(ForeignScanState *node); *************** ExecForeignScan(ForeignScanState *node) *** 93,98 **** --- 94,226 ---- (ExecScanRecheckMtd) ForeignRecheck); } + /* + * pseudo_column_walker + * + * helper routine of GetPseudoTupleDesc. It pulls Var nodes that reference + * pseudo columns from targetlis of the relation + */ + typedef struct + { + Relation relation; + Index varno; + List *pcolumns; + AttrNumber max_attno; + } pseudo_column_walker_context; + + static bool + pseudo_column_walker(Node *node, pseudo_column_walker_context *context) + { + if (node == NULL) + return false; + if (IsA(node, Var)) + { + Var *var = (Var *) node; + ListCell *cell; + + if (var->varno == context->varno && var->varlevelsup == 0 && + var->varattno > RelationGetNumberOfAttributes(context->relation)) + { + foreach (cell, context->pcolumns) + { + Var *temp = lfirst(cell); + + if (temp->varattno == var->varattno) + { + if (!equal(var, temp)) + elog(ERROR, "asymmetric pseudo column appeared"); + break; + } + } + if (!cell) + { + context->pcolumns = lappend(context->pcolumns, var); + if (var->varattno > context->max_attno) + context->max_attno = var->varattno; + } + } + return false; + } + + /* Should not find an unplanned subquery */ + Assert(!IsA(node, Query)); + + return expression_tree_walker(node, pseudo_column_walker, + (void *)context); + } + + /* + * GetPseudoTupleDesc + * + * It generates TupleDesc structure including pseudo-columns if required. + */ + static TupleDesc + GetPseudoTupleDesc(ForeignScan *node, Relation relation) + { + pseudo_column_walker_context context; + List *target_list = node->scan.plan.targetlist; + TupleDesc tupdesc; + AttrNumber attno; + ListCell *cell; + ListCell *prev; + bool hasoid; + + context.relation = relation; + context.varno = node->scan.scanrelid; + context.pcolumns = NIL; + context.max_attno = -1; + + pseudo_column_walker((Node *)target_list, (void *)&context); + Assert(context.max_attno > RelationGetNumberOfAttributes(relation)); + + hasoid = RelationGetForm(relation)->relhasoids; + tupdesc = CreateTemplateTupleDesc(context.max_attno, hasoid); + + for (attno = 1; attno <= context.max_attno; attno++) + { + /* case of regular columns */ + if (attno <= RelationGetNumberOfAttributes(relation)) + { + memcpy(tupdesc->attrs[attno - 1], + RelationGetDescr(relation)->attrs[attno - 1], + ATTRIBUTE_FIXED_PART_SIZE); + continue; + } + + /* case of pseudo columns */ + prev = NULL; + foreach (cell, context.pcolumns) + { + Var *var = lfirst(cell); + + if (var->varattno == attno) + { + char namebuf[NAMEDATALEN]; + + snprintf(namebuf, sizeof(namebuf), + "pseudo_column_%d", attno); + + TupleDescInitEntry(tupdesc, + attno, + namebuf, + var->vartype, + var->vartypmod, + 0); + TupleDescInitEntryCollation(tupdesc, + attno, + var->varcollid); + context.pcolumns + = list_delete_cell(context.pcolumns, cell, prev); + break; + } + prev = cell; + } + if (!cell) + elog(ERROR, "pseudo column %d of %s not in target list", + attno, RelationGetRelationName(relation)); + } + return tupdesc; + } /* ---------------------------------------------------------------- * ExecInitForeignScan *************** ExecInitForeignScan(ForeignScan *node, E *** 103,108 **** --- 231,237 ---- { ForeignScanState *scanstate; Relation currentRelation; + TupleDesc tupdesc; FdwRoutine *fdwroutine; /* check for unsupported flags */ *************** ExecInitForeignScan(ForeignScan *node, E *** 149,155 **** /* * get the scan type from the relation descriptor. */ ! ExecAssignScanType(&scanstate->ss, RelationGetDescr(currentRelation)); /* * Initialize result tuple type and projection info. --- 278,289 ---- /* * get the scan type from the relation descriptor. */ ! if (node->fsPseudoCol) ! tupdesc = GetPseudoTupleDesc(node, currentRelation); ! else ! tupdesc = RelationGetDescr(currentRelation); ! ! ExecAssignScanType(&scanstate->ss, tupdesc); /* * Initialize result tuple type and projection info. diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c new file mode 100644 index d31015c..bf9421e *** a/src/backend/executor/nodeModifyTable.c --- b/src/backend/executor/nodeModifyTable.c *************** *** 42,47 **** --- 42,48 ---- #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" *************** ExecInsert(TupleTableSlot *slot, *** 170,175 **** --- 171,177 ---- Relation resultRelationDesc; Oid newId; List *recheckIndexes = NIL; + int num_rows = 1; /* * get the heap tuple out of the tuple table slot, making sure we have a *************** ExecInsert(TupleTableSlot *slot, *** 225,230 **** --- 227,240 ---- newId = InvalidOid; } + else if (resultRelInfo->ri_fdwroutine) + { + FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine; + + num_rows = fdwroutine->ExecForeignInsert(resultRelInfo, tuple); + + newId = InvalidOid; + } else { /* *************** ExecInsert(TupleTableSlot *slot, *** 252,258 **** if (canSetTag) { ! (estate->es_processed)++; estate->es_lastoid = newId; setLastTid(&(tuple->t_self)); } --- 262,268 ---- if (canSetTag) { ! (estate->es_processed) += num_rows; estate->es_lastoid = newId; setLastTid(&(tuple->t_self)); } *************** ExecInsert(TupleTableSlot *slot, *** 285,291 **** * ---------------------------------------------------------------- */ static TupleTableSlot * ! ExecDelete(ItemPointer tupleid, HeapTupleHeader oldtuple, TupleTableSlot *planSlot, EPQState *epqstate, --- 295,301 ---- * ---------------------------------------------------------------- */ static TupleTableSlot * ! ExecDelete(Datum rowid, HeapTupleHeader oldtuple, TupleTableSlot *planSlot, EPQState *epqstate, *************** ExecDelete(ItemPointer tupleid, *** 296,301 **** --- 306,312 ---- Relation resultRelationDesc; HTSU_Result result; HeapUpdateFailureData hufd; + int num_rows = 1; /* * get information on the (current) result relation *************** ExecDelete(ItemPointer tupleid, *** 310,316 **** bool dodelete; dodelete = ExecBRDeleteTriggers(estate, epqstate, resultRelInfo, ! tupleid); if (!dodelete) /* "do nothing" */ return NULL; --- 321,327 ---- bool dodelete; dodelete = ExecBRDeleteTriggers(estate, epqstate, resultRelInfo, ! (ItemPointer)DatumGetPointer(rowid)); if (!dodelete) /* "do nothing" */ return NULL; *************** ExecDelete(ItemPointer tupleid, *** 334,341 **** --- 345,360 ---- if (!dodelete) /* "do nothing" */ return NULL; } + else if (resultRelInfo->ri_fdwroutine) + { + FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine; + + num_rows = fdwroutine->ExecForeignDelete(resultRelInfo, rowid); + } else { + ItemPointer tupleid = (ItemPointer) DatumGetPointer(rowid); + /* * delete the tuple * *************** ldelete:; *** 430,439 **** } if (canSetTag) ! (estate->es_processed)++; /* AFTER ROW DELETE Triggers */ ! ExecARDeleteTriggers(estate, resultRelInfo, tupleid); /* Process RETURNING if present */ if (resultRelInfo->ri_projectReturning) --- 449,459 ---- } if (canSetTag) ! (estate->es_processed) += num_rows; /* AFTER ROW DELETE Triggers */ ! ExecARDeleteTriggers(estate, resultRelInfo, ! (ItemPointer)DatumGetPointer(rowid)); /* Process RETURNING if present */ if (resultRelInfo->ri_projectReturning) *************** ldelete:; *** 457,463 **** } else { ! deltuple.t_self = *tupleid; if (!heap_fetch(resultRelationDesc, SnapshotAny, &deltuple, &delbuffer, false, NULL)) elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING"); --- 477,484 ---- } else { ! ItemPointerCopy((ItemPointer)DatumGetPointer(rowid), ! &deltuple.t_self); if (!heap_fetch(resultRelationDesc, SnapshotAny, &deltuple, &delbuffer, false, NULL)) elog(ERROR, "failed to fetch deleted tuple for DELETE RETURNING"); *************** ldelete:; *** 499,505 **** * ---------------------------------------------------------------- */ static TupleTableSlot * ! ExecUpdate(ItemPointer tupleid, HeapTupleHeader oldtuple, TupleTableSlot *slot, TupleTableSlot *planSlot, --- 520,526 ---- * ---------------------------------------------------------------- */ static TupleTableSlot * ! ExecUpdate(Datum rowid, HeapTupleHeader oldtuple, TupleTableSlot *slot, TupleTableSlot *planSlot, *************** ExecUpdate(ItemPointer tupleid, *** 513,518 **** --- 534,540 ---- HTSU_Result result; HeapUpdateFailureData hufd; List *recheckIndexes = NIL; + int num_rows = 1; /* * abort the operation if not running transactions *************** ExecUpdate(ItemPointer tupleid, *** 537,543 **** resultRelInfo->ri_TrigDesc->trig_update_before_row) { slot = ExecBRUpdateTriggers(estate, epqstate, resultRelInfo, ! tupleid, slot); if (slot == NULL) /* "do nothing" */ return NULL; --- 559,565 ---- resultRelInfo->ri_TrigDesc->trig_update_before_row) { slot = ExecBRUpdateTriggers(estate, epqstate, resultRelInfo, ! (ItemPointer)DatumGetPointer(rowid), slot); if (slot == NULL) /* "do nothing" */ return NULL; *************** ExecUpdate(ItemPointer tupleid, *** 567,574 **** --- 589,604 ---- /* trigger might have changed tuple */ tuple = ExecMaterializeSlot(slot); } + else if (resultRelInfo->ri_fdwroutine) + { + FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine; + + num_rows = fdwroutine->ExecForeignUpdate(resultRelInfo, rowid, tuple); + } else { + ItemPointer tupleid = (ItemPointer) DatumGetPointer(rowid); + /* * Check the constraints of the tuple * *************** lreplace:; *** 687,697 **** } if (canSetTag) ! (estate->es_processed)++; /* AFTER ROW UPDATE Triggers */ ! ExecARUpdateTriggers(estate, resultRelInfo, tupleid, tuple, ! recheckIndexes); list_free(recheckIndexes); --- 717,728 ---- } if (canSetTag) ! (estate->es_processed) += num_rows; /* AFTER ROW UPDATE Triggers */ ! ExecARUpdateTriggers(estate, resultRelInfo, ! (ItemPointer) DatumGetPointer(rowid), ! tuple, recheckIndexes); list_free(recheckIndexes); *************** ExecModifyTable(ModifyTableState *node) *** 771,776 **** --- 802,808 ---- TupleTableSlot *planSlot; ItemPointer tupleid = NULL; ItemPointerData tuple_ctid; + Datum rowid = 0; HeapTupleHeader oldtuple = NULL; /* *************** ExecModifyTable(ModifyTableState *node) *** 859,875 **** if (junkfilter != NULL) { /* ! * extract the 'ctid' or 'wholerow' junk attribute. */ if (operation == CMD_UPDATE || operation == CMD_DELETE) { Datum datum; bool isNull; ! if (resultRelInfo->ri_RelationDesc->rd_rel->relkind == RELKIND_RELATION) { datum = ExecGetJunkAttribute(slot, ! junkfilter->jf_junkAttNo, &isNull); /* shouldn't ever get a null result... */ if (isNull) --- 891,909 ---- if (junkfilter != NULL) { /* ! * extract the 'ctid', 'rowid' or 'wholerow' junk attribute. */ if (operation == CMD_UPDATE || operation == CMD_DELETE) { + char relkind; Datum datum; bool isNull; ! relkind = resultRelInfo->ri_RelationDesc->rd_rel->relkind; ! if (relkind == RELKIND_RELATION) { datum = ExecGetJunkAttribute(slot, ! junkfilter->jf_junkRowidNo, &isNull); /* shouldn't ever get a null result... */ if (isNull) *************** ExecModifyTable(ModifyTableState *node) *** 877,889 **** tupleid = (ItemPointer) DatumGetPointer(datum); tuple_ctid = *tupleid; /* be sure we don't free ! * ctid!! */ ! tupleid = &tuple_ctid; } else { datum = ExecGetJunkAttribute(slot, ! junkfilter->jf_junkAttNo, &isNull); /* shouldn't ever get a null result... */ if (isNull) --- 911,943 ---- tupleid = (ItemPointer) DatumGetPointer(datum); tuple_ctid = *tupleid; /* be sure we don't free ! * ctid ! */ ! rowid = PointerGetDatum(&tuple_ctid); ! } ! else if (relkind == RELKIND_FOREIGN_TABLE) ! { ! datum = ExecGetJunkAttribute(slot, ! junkfilter->jf_junkRowidNo, ! &isNull); ! /* shouldn't ever get a null result... */ ! if (isNull) ! elog(ERROR, "rowid is NULL"); ! ! rowid = datum; ! ! datum = ExecGetJunkAttribute(slot, ! junkfilter->jf_junkRecordNo, ! &isNull); ! /* shouldn't ever get a null result... */ ! if (isNull) ! elog(ERROR, "wholerow is NULL"); ! ! oldtuple = DatumGetHeapTupleHeader(datum); } else { datum = ExecGetJunkAttribute(slot, ! junkfilter->jf_junkRecordNo, &isNull); /* shouldn't ever get a null result... */ if (isNull) *************** ExecModifyTable(ModifyTableState *node) *** 906,916 **** slot = ExecInsert(slot, planSlot, estate, node->canSetTag); break; case CMD_UPDATE: ! slot = ExecUpdate(tupleid, oldtuple, slot, planSlot, &node->mt_epqstate, estate, node->canSetTag); break; case CMD_DELETE: ! slot = ExecDelete(tupleid, oldtuple, planSlot, &node->mt_epqstate, estate, node->canSetTag); break; default: --- 960,970 ---- slot = ExecInsert(slot, planSlot, estate, node->canSetTag); break; case CMD_UPDATE: ! slot = ExecUpdate(rowid, oldtuple, slot, planSlot, &node->mt_epqstate, estate, node->canSetTag); break; case CMD_DELETE: ! slot = ExecDelete(rowid, oldtuple, planSlot, &node->mt_epqstate, estate, node->canSetTag); break; default: *************** ExecInitModifyTable(ModifyTable *node, E *** 997,1002 **** --- 1051,1058 ---- i = 0; foreach(l, node->plans) { + char relkind; + subplan = (Plan *) lfirst(l); /* *************** ExecInitModifyTable(ModifyTable *node, E *** 1022,1027 **** --- 1078,1101 ---- estate->es_result_relation_info = resultRelInfo; mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags); + /* + * Also tells FDW extensions to init the plan for this result rel + */ + relkind = RelationGetForm(resultRelInfo->ri_RelationDesc)->relkind; + if (relkind == RELKIND_FOREIGN_TABLE) + { + Oid relid = RelationGetRelid(resultRelInfo->ri_RelationDesc); + FdwRoutine *fdwroutine = GetFdwRoutineByRelId(relid); + List *fdwprivate = list_nth(node->fdwPrivList, i); + + Assert(fdwroutine != NULL); + resultRelInfo->ri_fdwroutine = fdwroutine; + resultRelInfo->ri_fdw_state = NULL; + + if (fdwroutine->BeginForeignModify) + fdwroutine->BeginForeignModify(mtstate, resultRelInfo, + fdwprivate, subplan, eflags); + } resultRelInfo++; i++; } *************** ExecInitModifyTable(ModifyTable *node, E *** 1163,1168 **** --- 1237,1244 ---- for (i = 0; i < nplans; i++) { JunkFilter *j; + char relkind = + RelationGetForm(resultRelInfo->ri_RelationDesc)->relkind; subplan = mtstate->mt_plans[i]->plan; if (operation == CMD_INSERT || operation == CMD_UPDATE) *************** ExecInitModifyTable(ModifyTable *node, E *** 1176,1191 **** 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) { ! j->jf_junkAttNo = ExecFindJunkAttribute(j, "ctid"); ! if (!AttributeNumberIsValid(j->jf_junkAttNo)) elog(ERROR, "could not find junk ctid column"); } else { ! j->jf_junkAttNo = ExecFindJunkAttribute(j, "wholerow"); ! if (!AttributeNumberIsValid(j->jf_junkAttNo)) elog(ERROR, "could not find junk wholerow column"); } } --- 1252,1278 ---- if (operation == CMD_UPDATE || operation == CMD_DELETE) { /* For UPDATE/DELETE, find the appropriate junk attr now */ ! if (relkind == RELKIND_RELATION) { ! j->jf_junkRowidNo = ExecFindJunkAttribute(j, "ctid"); ! if (!AttributeNumberIsValid(j->jf_junkRowidNo)) elog(ERROR, "could not find junk ctid column"); } + else if (relkind == RELKIND_FOREIGN_TABLE) + { + j->jf_junkRowidNo = ExecFindJunkAttribute(j, "rowid"); + if (!AttributeNumberIsValid(j->jf_junkRowidNo)) + elog(ERROR, "could not find junk rowid column"); + j->jf_junkRecordNo + = ExecFindJunkAttribute(j, "record"); + if (!AttributeNumberIsValid(j->jf_junkRecordNo)) + elog(ERROR, "could not find junk wholerow column"); + } else { ! j->jf_junkRecordNo ! = ExecFindJunkAttribute(j, "wholerow"); ! if (!AttributeNumberIsValid(j->jf_junkRecordNo)) elog(ERROR, "could not find junk wholerow column"); } } *************** ExecEndModifyTable(ModifyTableState *nod *** 1239,1244 **** --- 1326,1346 ---- { int i; + /* Let the FDW shut dowm */ + for (i=0; i < node->ps.state->es_num_result_relations; i++) + { + ResultRelInfo *resultRelInfo + = &node->ps.state->es_result_relations[i]; + + if (resultRelInfo->ri_fdwroutine) + { + FdwRoutine *fdwroutine = resultRelInfo->ri_fdwroutine; + + if (fdwroutine->EndForeignModify) + fdwroutine->EndForeignModify(resultRelInfo); + } + } + /* * Free the exprcontext */ diff --git a/src/backend/foreign/foreign.c b/src/backend/foreign/foreign.c new file mode 100644 index d8845aa..4363a28 *** a/src/backend/foreign/foreign.c --- b/src/backend/foreign/foreign.c *************** get_foreign_server_oid(const char *serve *** 571,573 **** --- 571,641 ---- errmsg("server \"%s\" does not exist", servername))); return oid; } + + /* + * get_pseudo_rowid_column + * + * It picks up an attribute number to be used for the pseudo rowid column, + * if it exists. It should be injected at rewriteHandler.c if the supplied query + * is a UPDATE or DELETE command. Elsewhere, it returns InvalidAttrNumber. + */ + AttrNumber + get_pseudo_rowid_column(RelOptInfo *baserel, List *targetList) + { + ListCell *cell; + + foreach (cell, targetList) + { + TargetEntry *tle = lfirst(cell); + + if (tle->resjunk && + tle->resname && strcmp(tle->resname, "rowid") == 0) + { + Var *var; + + if (!IsA(tle->expr, Var)) + elog(ERROR, "unexpected node on junk rowid entry: %d", + (int) nodeTag(tle->expr)); + + var = (Var *) tle->expr; + if (baserel->relid == var->varno) + return var->varattno; + } + } + return InvalidAttrNumber; + } + + /* + * lookup_foreign_scan_plan + * + * It looks up the ForeignScan plan node with the supplied range-table id. + * Its typical usage is for PlanForeignModify to get the underlying scan plan on + * UPDATE or DELETE commands. + */ + ForeignScan * + lookup_foreign_scan_plan(Plan *subplan, Index rtindex) + { + if (!subplan) + return NULL; + + else if (IsA(subplan, ForeignScan)) + { + ForeignScan *fscan = (ForeignScan *) subplan; + + if (fscan->scan.scanrelid == rtindex) + return fscan; + } + else + { + ForeignScan *fscan; + + fscan = lookup_foreign_scan_plan(subplan->lefttree, rtindex); + if (fscan != NULL) + return fscan; + + fscan = lookup_foreign_scan_plan(subplan->righttree, rtindex); + if (fscan != NULL) + return fscan; + } + return NULL; + } diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c new file mode 100644 index 9387ee9..5b1e8ac *** a/src/backend/nodes/copyfuncs.c --- b/src/backend/nodes/copyfuncs.c *************** _copyModifyTable(const ModifyTable *from *** 181,186 **** --- 181,187 ---- COPY_NODE_FIELD(returningLists); COPY_NODE_FIELD(rowMarks); COPY_SCALAR_FIELD(epqParam); + COPY_NODE_FIELD(fdwPrivList); return newnode; } *************** _copyForeignScan(const ForeignScan *from *** 594,599 **** --- 595,601 ---- COPY_NODE_FIELD(fdw_exprs); COPY_NODE_FIELD(fdw_private); COPY_SCALAR_FIELD(fsSystemCol); + COPY_SCALAR_FIELD(fsPseudoCol); return newnode; } diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c new file mode 100644 index 35c6287..c32ebd3 *** a/src/backend/nodes/outfuncs.c --- b/src/backend/nodes/outfuncs.c *************** _outModifyTable(StringInfo str, const Mo *** 335,340 **** --- 335,341 ---- WRITE_NODE_FIELD(returningLists); WRITE_NODE_FIELD(rowMarks); WRITE_INT_FIELD(epqParam); + WRITE_NODE_FIELD(fdwPrivList); } static void *************** _outForeignScan(StringInfo str, const Fo *** 562,567 **** --- 563,569 ---- WRITE_NODE_FIELD(fdw_exprs); WRITE_NODE_FIELD(fdw_private); WRITE_BOOL_FIELD(fsSystemCol); + WRITE_BOOL_FIELD(fsPseudoCol); } static void diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c new file mode 100644 index 030f420..91db1b8 *** a/src/backend/optimizer/plan/createplan.c --- b/src/backend/optimizer/plan/createplan.c *************** *** 39,44 **** --- 39,45 ---- #include "parser/parse_clause.h" #include "parser/parsetree.h" #include "utils/lsyscache.h" + #include "utils/rel.h" static Plan *create_plan_recurse(PlannerInfo *root, Path *best_path); *************** create_foreignscan_plan(PlannerInfo *roo *** 1943,1948 **** --- 1944,1951 ---- RelOptInfo *rel = best_path->path.parent; Index scan_relid = rel->relid; RangeTblEntry *rte; + Relation relation; + AttrNumber num_attrs; int i; /* it should be a base rel... */ *************** create_foreignscan_plan(PlannerInfo *roo *** 2001,2006 **** --- 2004,2025 ---- } } + /* + * Also, detect whether any pseudo columns are requested from rel. + */ + relation = heap_open(rte->relid, NoLock); + scan_plan->fsPseudoCol = false; + num_attrs = RelationGetNumberOfAttributes(relation); + for (i = num_attrs + 1; i <= rel->max_attr; i++) + { + if (!bms_is_empty(rel->attr_needed[i - rel->min_attr])) + { + scan_plan->fsPseudoCol = true; + break; + } + } + heap_close(relation, NoLock); + return scan_plan; } *************** make_result(PlannerInfo *root, *** 4695,4701 **** * to make it look better sometime. */ ModifyTable * ! make_modifytable(CmdType operation, bool canSetTag, List *resultRelations, List *subplans, List *returningLists, List *rowMarks, int epqParam) --- 4714,4721 ---- * to make it look better sometime. */ ModifyTable * ! make_modifytable(PlannerInfo *root, ! CmdType operation, bool canSetTag, List *resultRelations, List *subplans, List *returningLists, List *rowMarks, int epqParam) *************** make_modifytable(CmdType operation, bool *** 4704,4709 **** --- 4724,4731 ---- Plan *plan = &node->plan; double total_size; ListCell *subnode; + ListCell *resultRel; + List *fdw_priv_list = NIL; Assert(list_length(resultRelations) == list_length(subplans)); Assert(returningLists == NIL || *************** make_modifytable(CmdType operation, bool *** 4746,4751 **** --- 4768,4797 ---- node->rowMarks = rowMarks; node->epqParam = epqParam; + /* + * Allow FDW driver to construct its private plan if the result relation + * is a foreign table. + */ + forboth (resultRel, resultRelations, subnode, subplans) + { + RangeTblEntry *rte = rt_fetch(lfirst_int(resultRel), + root->parse->rtable); + List *fdw_private = NIL; + char relkind = get_rel_relkind(rte->relid); + + if (relkind == RELKIND_FOREIGN_TABLE) + { + FdwRoutine *fdwroutine = GetFdwRoutineByRelId(rte->relid); + + if (fdwroutine && fdwroutine->PlanForeignModify) + fdw_private = fdwroutine->PlanForeignModify(root, node, + lfirst_int(resultRel), + (Plan *) lfirst(subnode)); + } + fdw_priv_list = lappend(fdw_priv_list, fdw_private); + } + node->fdwPrivList = fdw_priv_list; + return node; } diff --git a/src/backend/optimizer/plan/initsplan.c b/src/backend/optimizer/plan/initsplan.c new file mode 100644 index bd719b5..57ba192 *** a/src/backend/optimizer/plan/initsplan.c --- b/src/backend/optimizer/plan/initsplan.c *************** static void check_hashjoinable(RestrictI *** 85,91 **** * "other rel" RelOptInfos for the members of any appendrels we find here.) */ void ! add_base_rels_to_query(PlannerInfo *root, Node *jtnode) { if (jtnode == NULL) return; --- 85,91 ---- * "other rel" RelOptInfos for the members of any appendrels we find here.) */ void ! add_base_rels_to_query(PlannerInfo *root, List *tlist, Node *jtnode) { if (jtnode == NULL) return; *************** add_base_rels_to_query(PlannerInfo *root *** 93,99 **** { int varno = ((RangeTblRef *) jtnode)->rtindex; ! (void) build_simple_rel(root, varno, RELOPT_BASEREL); } else if (IsA(jtnode, FromExpr)) { --- 93,99 ---- { int varno = ((RangeTblRef *) jtnode)->rtindex; ! (void) build_simple_rel(root, varno, tlist, RELOPT_BASEREL); } else if (IsA(jtnode, FromExpr)) { *************** add_base_rels_to_query(PlannerInfo *root *** 101,114 **** ListCell *l; foreach(l, f->fromlist) ! add_base_rels_to_query(root, lfirst(l)); } else if (IsA(jtnode, JoinExpr)) { JoinExpr *j = (JoinExpr *) jtnode; ! add_base_rels_to_query(root, j->larg); ! add_base_rels_to_query(root, j->rarg); } else elog(ERROR, "unrecognized node type: %d", --- 101,114 ---- ListCell *l; foreach(l, f->fromlist) ! add_base_rels_to_query(root, tlist, lfirst(l)); } else if (IsA(jtnode, JoinExpr)) { JoinExpr *j = (JoinExpr *) jtnode; ! add_base_rels_to_query(root, tlist, j->larg); ! add_base_rels_to_query(root, tlist, j->rarg); } else elog(ERROR, "unrecognized node type: %d", diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c new file mode 100644 index c2488a4..4442444 *** a/src/backend/optimizer/plan/planmain.c --- b/src/backend/optimizer/plan/planmain.c *************** query_planner(PlannerInfo *root, List *t *** 112,117 **** --- 112,120 ---- */ if (parse->jointree->fromlist == NIL) { + /* Make a flattened version of the rangetable for faster access */ + setup_simple_rel_arrays(root); + /* We need a trivial path result */ *cheapest_path = (Path *) create_result_path((List *) parse->jointree->quals); *************** query_planner(PlannerInfo *root, List *t *** 163,169 **** * rangetable may contain RTEs for rels not actively part of the query, * for example views. We don't want to make RelOptInfos for them. */ ! add_base_rels_to_query(root, (Node *) parse->jointree); /* * Examine the targetlist and join tree, adding entries to baserel --- 166,172 ---- * rangetable may contain RTEs for rels not actively part of the query, * for example views. We don't want to make RelOptInfos for them. */ ! add_base_rels_to_query(root, tlist, (Node *) parse->jointree); /* * Examine the targetlist and join tree, adding entries to baserel diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c new file mode 100644 index b61005f..2d72244 *** a/src/backend/optimizer/plan/planner.c --- b/src/backend/optimizer/plan/planner.c *************** subquery_planner(PlannerGlobal *glob, Qu *** 571,577 **** else rowMarks = root->rowMarks; ! plan = (Plan *) make_modifytable(parse->commandType, parse->canSetTag, list_make1_int(parse->resultRelation), list_make1(plan), --- 571,578 ---- else rowMarks = root->rowMarks; ! plan = (Plan *) make_modifytable(root, ! parse->commandType, parse->canSetTag, list_make1_int(parse->resultRelation), list_make1(plan), *************** inheritance_planner(PlannerInfo *root) *** 964,970 **** rowMarks = root->rowMarks; /* And last, tack on a ModifyTable node to do the UPDATE/DELETE work */ ! return (Plan *) make_modifytable(parse->commandType, parse->canSetTag, resultRelations, subplans, --- 965,972 ---- rowMarks = root->rowMarks; /* And last, tack on a ModifyTable node to do the UPDATE/DELETE work */ ! return (Plan *) make_modifytable(root, ! parse->commandType, parse->canSetTag, resultRelations, subplans, *************** plan_cluster_use_sort(Oid tableOid, Oid *** 3385,3391 **** setup_simple_rel_arrays(root); /* Build RelOptInfo */ ! rel = build_simple_rel(root, 1, RELOPT_BASEREL); /* Locate IndexOptInfo for the target index */ indexInfo = NULL; --- 3387,3393 ---- setup_simple_rel_arrays(root); /* Build RelOptInfo */ ! rel = build_simple_rel(root, 1, NIL, RELOPT_BASEREL); /* Locate IndexOptInfo for the target index */ indexInfo = NULL; diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c new file mode 100644 index b91e9f4..61bc447 *** a/src/backend/optimizer/prep/prepunion.c --- b/src/backend/optimizer/prep/prepunion.c *************** recurse_set_operations(Node *setOp, Plan *** 236,242 **** * used for anything here, but it carries the subroot data structures * forward to setrefs.c processing. */ ! rel = build_simple_rel(root, rtr->rtindex, RELOPT_BASEREL); /* plan_params should not be in use in current query level */ Assert(root->plan_params == NIL); --- 236,243 ---- * used for anything here, but it carries the subroot data structures * forward to setrefs.c processing. */ ! rel = build_simple_rel(root, rtr->rtindex, refnames_tlist, ! RELOPT_BASEREL); /* plan_params should not be in use in current query level */ Assert(root->plan_params == NIL); diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c new file mode 100644 index 04d5028..7d6bc4b *** a/src/backend/optimizer/util/plancat.c --- b/src/backend/optimizer/util/plancat.c *************** *** 25,30 **** --- 25,31 ---- #include "access/xlog.h" #include "catalog/catalog.h" #include "catalog/heap.h" + #include "foreign/fdwapi.h" #include "miscadmin.h" #include "nodes/makefuncs.h" #include "optimizer/clauses.h" *************** static List *build_index_tlist(PlannerIn *** 80,86 **** */ void get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, ! RelOptInfo *rel) { Index varno = rel->relid; Relation relation; --- 81,87 ---- */ void get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent, ! List *tlist, RelOptInfo *rel) { Index varno = rel->relid; Relation relation; *************** get_relation_info(PlannerInfo *root, Oid *** 104,109 **** --- 105,134 ---- rel->max_attr = RelationGetNumberOfAttributes(relation); rel->reltablespace = RelationGetForm(relation)->reltablespace; + /* + * Adjust width of attr_needed slot in case FDW extension wants + * to return pseudo-columns in addition to the columns in its + * table definition. + * GetForeignRelWidth, an optional FDW handler, enables a FDW + * to save properties of a pseudo-column in its private field. + * When the foreign table is the target of UPDATE/DELETE, the query rewriter + * injects a "rowid" pseudo-column to track the remote row to be modified, + * so the FDW has to track which varattno shall perform as "rowid". + */ + if (RelationGetForm(relation)->relkind == RELKIND_FOREIGN_TABLE) + { + FdwRoutine *fdwroutine = GetFdwRoutineByRelId(relationObjectId); + + if (fdwroutine->GetForeignRelWidth) + { + rel->max_attr = fdwroutine->GetForeignRelWidth(root, rel, + relation, + inhparent, + tlist); + Assert(rel->max_attr >= RelationGetNumberOfAttributes(relation)); + } + } + Assert(rel->max_attr >= rel->min_attr); rel->attr_needed = (Relids *) palloc0((rel->max_attr - rel->min_attr + 1) * sizeof(Relids)); diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c new file mode 100644 index f724714..84b674c *** a/src/backend/optimizer/util/relnode.c --- b/src/backend/optimizer/util/relnode.c *************** setup_simple_rel_arrays(PlannerInfo *roo *** 80,86 **** * Construct a new RelOptInfo for a base relation or 'other' relation. */ RelOptInfo * ! build_simple_rel(PlannerInfo *root, int relid, RelOptKind reloptkind) { RelOptInfo *rel; RangeTblEntry *rte; --- 80,87 ---- * Construct a new RelOptInfo for a base relation or 'other' relation. */ RelOptInfo * ! build_simple_rel(PlannerInfo *root, int relid, List *tlist, ! RelOptKind reloptkind) { RelOptInfo *rel; RangeTblEntry *rte; *************** build_simple_rel(PlannerInfo *root, int *** 133,139 **** { case RTE_RELATION: /* Table --- retrieve statistics from the system catalogs */ ! get_relation_info(root, rte->relid, rte->inh, rel); break; case RTE_SUBQUERY: case RTE_FUNCTION: --- 134,140 ---- { case RTE_RELATION: /* Table --- retrieve statistics from the system catalogs */ ! get_relation_info(root, rte->relid, rte->inh, tlist, rel); break; case RTE_SUBQUERY: case RTE_FUNCTION: *************** build_simple_rel(PlannerInfo *root, int *** 180,186 **** if (appinfo->parent_relid != relid) continue; ! (void) build_simple_rel(root, appinfo->child_relid, RELOPT_OTHER_MEMBER_REL); } } --- 181,187 ---- if (appinfo->parent_relid != relid) continue; ! (void) build_simple_rel(root, appinfo->child_relid, tlist, RELOPT_OTHER_MEMBER_REL); } } diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c new file mode 100644 index b785c26..6cd6241 *** a/src/backend/rewrite/rewriteHandler.c --- b/src/backend/rewrite/rewriteHandler.c *************** rewriteTargetListUD(Query *parsetree, Ra *** 1165,1171 **** Relation target_relation) { Var *var; ! const char *attrname; TargetEntry *tle; if (target_relation->rd_rel->relkind == RELKIND_RELATION) --- 1165,1174 ---- Relation target_relation) { Var *var; ! List *varList; ! List *attNameList; ! ListCell *cell1; ! ListCell *cell2; TargetEntry *tle; if (target_relation->rd_rel->relkind == RELKIND_RELATION) *************** rewriteTargetListUD(Query *parsetree, Ra *** 1179,1186 **** -1, InvalidOid, 0); ! attrname = "ctid"; } else { --- 1182,1216 ---- -1, InvalidOid, 0); + varList = list_make1(var); + attNameList = list_make1("ctid"); + } + else if (target_relation->rd_rel->relkind == RELKIND_FOREIGN_TABLE) + { + /* + * Emit Rowid so that executor can find the row to update or delete. + */ + var = makeVar(parsetree->resultRelation, + RelationGetNumberOfAttributes(target_relation) + 1, + CSTRINGOID, + -2, + InvalidOid, + 0); + varList = list_make1(var); ! /* ! * Emit generic record Var so that executor will have the "old" view ! * row to pass the RETURNING clause (or upcoming triggers). ! */ ! var = makeVar(parsetree->resultRelation, ! InvalidAttrNumber, ! RECORDOID, ! -1, ! InvalidOid, ! 0); ! varList = lappend(varList, var); ! ! attNameList = list_make2("rowid", "record"); } else { *************** rewriteTargetListUD(Query *parsetree, Ra *** 1192,1207 **** parsetree->resultRelation, 0, false); ! ! attrname = "wholerow"; } ! tle = makeTargetEntry((Expr *) var, ! list_length(parsetree->targetList) + 1, ! pstrdup(attrname), ! true); ! ! parsetree->targetList = lappend(parsetree->targetList, tle); } --- 1222,1242 ---- parsetree->resultRelation, 0, false); ! varList = list_make1(var); ! attNameList = list_make1("wholerow"); } ! /* ! * Append them to targetList ! */ ! forboth (cell1, varList, cell2, attNameList) ! { ! tle = makeTargetEntry((Expr *)lfirst(cell1), ! list_length(parsetree->targetList) + 1, ! pstrdup((const char *)lfirst(cell2)), ! true); ! parsetree->targetList = lappend(parsetree->targetList, tle); ! } } diff --git a/src/include/foreign/fdwapi.h b/src/include/foreign/fdwapi.h new file mode 100644 index 721cd25..153932a *** a/src/include/foreign/fdwapi.h --- b/src/include/foreign/fdwapi.h *************** *** 14,19 **** --- 14,20 ---- #include "nodes/execnodes.h" #include "nodes/relation.h" + #include "utils/rel.h" /* To avoid including explain.h here, reference ExplainState thus: */ struct ExplainState; *************** struct ExplainState; *** 22,27 **** --- 23,33 ---- /* * Callback function signatures --- see fdwhandler.sgml for more info. */ + typedef AttrNumber (*GetForeignRelWidth_function) (PlannerInfo *root, + RelOptInfo *baserel, + Relation foreignrel, + bool inhparent, + List *targetList); typedef void (*GetForeignRelSize_function) (PlannerInfo *root, RelOptInfo *baserel, *************** typedef int (*AcquireSampleRowsFunc) (Re *** 58,63 **** --- 64,87 ---- typedef bool (*AnalyzeForeignTable_function) (Relation relation, AcquireSampleRowsFunc *func, BlockNumber *totalpages); + typedef List *(*PlanForeignModify_function) (PlannerInfo *root, + ModifyTable *plan, + Index resultRelation, + Plan *subplan); + + typedef void (*BeginForeignModify_function) (ModifyTableState *mtstate, + ResultRelInfo *resultRelInfo, + List *fdw_private, + Plan *subplan, + int eflags); + typedef int (*ExecForeignInsert_function) (ResultRelInfo *resultRelInfo, + HeapTuple tuple); + typedef int (*ExecForeignDelete_function) (ResultRelInfo *resultRelInfo, + Datum rowid); + typedef int (*ExecForeignUpdate_function) (ResultRelInfo *resultRelInfo, + Datum rowid, + HeapTuple tuple); + typedef void (*EndForeignModify_function) (ResultRelInfo *resultRelInfo); /* * FdwRoutine is the struct returned by a foreign-data wrapper's handler *************** typedef struct FdwRoutine *** 90,95 **** --- 114,126 ---- * not provided. */ AnalyzeForeignTable_function AnalyzeForeignTable; + GetForeignRelWidth_function GetForeignRelWidth; + PlanForeignModify_function PlanForeignModify; + BeginForeignModify_function BeginForeignModify; + ExecForeignInsert_function ExecForeignInsert; + ExecForeignDelete_function ExecForeignDelete; + ExecForeignUpdate_function ExecForeignUpdate; + EndForeignModify_function EndForeignModify; } FdwRoutine; diff --git a/src/include/foreign/foreign.h b/src/include/foreign/foreign.h new file mode 100644 index f8aa99e..b63000a *** a/src/include/foreign/foreign.h --- b/src/include/foreign/foreign.h *************** *** 14,19 **** --- 14,21 ---- #define FOREIGN_H #include "nodes/parsenodes.h" + #include "nodes/plannodes.h" + #include "nodes/relation.h" /* Helper for obtaining username for user mapping */ *************** extern List *GetForeignColumnOptions(Oid *** 81,84 **** --- 83,90 ---- extern Oid get_foreign_data_wrapper_oid(const char *fdwname, bool missing_ok); extern Oid get_foreign_server_oid(const char *servername, bool missing_ok); + extern AttrNumber get_pseudo_rowid_column(RelOptInfo *baserel, + List *targetList); + extern ForeignScan *lookup_foreign_scan_plan(Plan *subplan, + Index rtindex); #endif /* FOREIGN_H */ diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h new file mode 100644 index d4911bd..18ab231 *** a/src/include/nodes/execnodes.h --- b/src/include/nodes/execnodes.h *************** typedef struct ProjectionInfo *** 268,276 **** * attribute numbers of the "original" tuple and the * attribute numbers of the "clean" tuple. * resultSlot: tuple slot used to hold cleaned tuple. ! * junkAttNo: not used by junkfilter code. Can be used by caller ! * to remember the attno of a specific junk attribute * (execMain.c stores the "ctid" attno here). * ---------------- */ typedef struct JunkFilter --- 268,278 ---- * attribute numbers of the "original" tuple and the * attribute numbers of the "clean" tuple. * resultSlot: tuple slot used to hold cleaned tuple. ! * jf_junkRowidNo: not used by junkfilter code. Can be used by caller ! * to remember the attno used to track a particular tuple ! * being updated or deleted. * (execMain.c stores the "ctid" attno here). + * jf_junkRecordNo: Also, the attno of whole-row reference. * ---------------- */ typedef struct JunkFilter *************** typedef struct JunkFilter *** 280,286 **** TupleDesc jf_cleanTupType; AttrNumber *jf_cleanMap; TupleTableSlot *jf_resultSlot; ! AttrNumber jf_junkAttNo; } JunkFilter; /* ---------------- --- 282,289 ---- TupleDesc jf_cleanTupType; AttrNumber *jf_cleanMap; TupleTableSlot *jf_resultSlot; ! AttrNumber jf_junkRowidNo; ! AttrNumber jf_junkRecordNo; } JunkFilter; /* ---------------- *************** typedef struct JunkFilter *** 303,308 **** --- 306,313 ---- * ConstraintExprs array of constraint-checking expr states * junkFilter for removing junk attributes from tuples * projectReturning for computing a RETURNING list + * fdwroutine FDW callbacks if foreign table + * fdw_state opaque state of FDW module, or NULL * ---------------- */ typedef struct ResultRelInfo *************** typedef struct ResultRelInfo *** 320,325 **** --- 325,332 ---- List **ri_ConstraintExprs; JunkFilter *ri_junkFilter; ProjectionInfo *ri_projectReturning; + struct FdwRoutine *ri_fdwroutine; + void *ri_fdw_state; } ResultRelInfo; /* ---------------- diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h new file mode 100644 index fb9a863..93481aa *** a/src/include/nodes/plannodes.h --- b/src/include/nodes/plannodes.h *************** typedef struct ModifyTable *** 175,180 **** --- 175,181 ---- List *returningLists; /* per-target-table RETURNING tlists */ List *rowMarks; /* PlanRowMarks (non-locking only) */ int epqParam; /* ID of Param for EvalPlanQual re-eval */ + List *fdwPrivList; /* private fields for foreign tables */ } ModifyTable; /* ---------------- *************** typedef struct ForeignScan *** 478,483 **** --- 479,485 ---- List *fdw_exprs; /* expressions that FDW may evaluate */ List *fdw_private; /* private data for FDW */ bool fsSystemCol; /* true if any "system column" is needed */ + bool fsPseudoCol; /* true if any "pseudo column" is needed */ } ForeignScan; diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h new file mode 100644 index de889fb..adfc93d *** a/src/include/optimizer/pathnode.h --- b/src/include/optimizer/pathnode.h *************** extern Path *reparameterize_path(Planner *** 133,139 **** */ extern void setup_simple_rel_arrays(PlannerInfo *root); extern RelOptInfo *build_simple_rel(PlannerInfo *root, int relid, ! RelOptKind reloptkind); extern RelOptInfo *find_base_rel(PlannerInfo *root, int relid); extern RelOptInfo *find_join_rel(PlannerInfo *root, Relids relids); extern RelOptInfo *build_join_rel(PlannerInfo *root, --- 133,139 ---- */ extern void setup_simple_rel_arrays(PlannerInfo *root); extern RelOptInfo *build_simple_rel(PlannerInfo *root, int relid, ! List *tlist, RelOptKind reloptkind); extern RelOptInfo *find_base_rel(PlannerInfo *root, int relid); extern RelOptInfo *find_join_rel(PlannerInfo *root, Relids relids); extern RelOptInfo *build_join_rel(PlannerInfo *root, diff --git a/src/include/optimizer/plancat.h b/src/include/optimizer/plancat.h new file mode 100644 index e0d04db..c5fce41 *** a/src/include/optimizer/plancat.h --- b/src/include/optimizer/plancat.h *************** extern PGDLLIMPORT get_relation_info_hoo *** 26,32 **** extern void get_relation_info(PlannerInfo *root, Oid relationObjectId, ! bool inhparent, RelOptInfo *rel); extern void estimate_rel_size(Relation rel, int32 *attr_widths, BlockNumber *pages, double *tuples, double *allvisfrac); --- 26,32 ---- extern void get_relation_info(PlannerInfo *root, Oid relationObjectId, ! bool inhparent, List *tlist, RelOptInfo *rel); extern void estimate_rel_size(Relation rel, int32 *attr_widths, BlockNumber *pages, double *tuples, double *allvisfrac); diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h new file mode 100644 index 0fe696c..746a603 *** a/src/include/optimizer/planmain.h --- b/src/include/optimizer/planmain.h *************** extern SetOp *make_setop(SetOpCmd cmd, S *** 79,85 **** long numGroups, double outputRows); extern Result *make_result(PlannerInfo *root, List *tlist, Node *resconstantqual, Plan *subplan); ! extern ModifyTable *make_modifytable(CmdType operation, bool canSetTag, List *resultRelations, List *subplans, List *returningLists, List *rowMarks, int epqParam); extern bool is_projection_capable_plan(Plan *plan); --- 79,86 ---- long numGroups, double outputRows); extern Result *make_result(PlannerInfo *root, List *tlist, Node *resconstantqual, Plan *subplan); ! 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); *************** extern bool is_projection_capable_plan(P *** 90,96 **** extern int from_collapse_limit; extern int join_collapse_limit; ! extern void add_base_rels_to_query(PlannerInfo *root, Node *jtnode); extern void build_base_rel_tlists(PlannerInfo *root, List *final_tlist); extern void add_vars_to_targetlist(PlannerInfo *root, List *vars, Relids where_needed, bool create_new_ph); --- 91,98 ---- extern int from_collapse_limit; extern int join_collapse_limit; ! extern void add_base_rels_to_query(PlannerInfo *root, List *tlist, ! Node *jtnode); extern void build_base_rel_tlists(PlannerInfo *root, List *final_tlist); extern void add_vars_to_targetlist(PlannerInfo *root, List *vars, Relids where_needed, bool create_new_ph);