Andres Freund wrote: > [09] Adjust all *Satisfies routines to take a HeapTuple instead of a > HeapTupleHeader > > For timetravel access to the catalog we need to be able to lookup (cmin, > cmax) pairs of catalog rows when were 'inside' that TX. This patch just > adapts the signature of the *Satisfies routines to expect a HeapTuple > instead of a HeapTupleHeader. The amount of changes for that is fairly > low as the HeapTupleSatisfiesVisibility macro already expected the > former. > > It also makes sure the HeapTuple fields are setup in the few places that > didn't already do so.
I had a look at this part. Running the regression tests unveiled a case where the tableOid wasn't being set (and thus caused an assertion to fail), so I added that. I also noticed that the additions to pruneheap.c are sometimes filling a tuple before it's strictly necessary, leading to wasted work. Moved those too. Looks good to me as attached. -- Álvaro Herrera http://www.2ndQuadrant.com/ PostgreSQL Development, 24x7 Support, Training & Services
*** a/contrib/pgrowlocks/pgrowlocks.c --- b/contrib/pgrowlocks/pgrowlocks.c *************** *** 120,126 **** pgrowlocks(PG_FUNCTION_ARGS) /* must hold a buffer lock to call HeapTupleSatisfiesUpdate */ LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE); ! if (HeapTupleSatisfiesUpdate(tuple->t_data, GetCurrentCommandId(false), scan->rs_cbuf) == HeapTupleBeingUpdated) { --- 120,126 ---- /* must hold a buffer lock to call HeapTupleSatisfiesUpdate */ LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE); ! if (HeapTupleSatisfiesUpdate(tuple, GetCurrentCommandId(false), scan->rs_cbuf) == HeapTupleBeingUpdated) { *** a/src/backend/access/heap/heapam.c --- b/src/backend/access/heap/heapam.c *************** *** 291,296 **** heapgetpage(HeapScanDesc scan, BlockNumber page) --- 291,297 ---- loctup.t_data = (HeapTupleHeader) PageGetItem((Page) dp, lpp); loctup.t_len = ItemIdGetLength(lpp); + loctup.t_tableOid = RelationGetRelid(scan->rs_rd); ItemPointerSet(&(loctup.t_self), page, lineoff); if (all_visible) *************** *** 1603,1609 **** heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer, heapTuple->t_data = (HeapTupleHeader) PageGetItem(dp, lp); heapTuple->t_len = ItemIdGetLength(lp); ! heapTuple->t_tableOid = relation->rd_id; heapTuple->t_self = *tid; /* --- 1604,1610 ---- heapTuple->t_data = (HeapTupleHeader) PageGetItem(dp, lp); heapTuple->t_len = ItemIdGetLength(lp); ! heapTuple->t_tableOid = RelationGetRelid(relation); heapTuple->t_self = *tid; /* *************** *** 1651,1657 **** heap_hot_search_buffer(ItemPointer tid, Relation relation, Buffer buffer, * transactions. */ if (all_dead && *all_dead && ! !HeapTupleIsSurelyDead(heapTuple->t_data, RecentGlobalXmin)) *all_dead = false; /* --- 1652,1658 ---- * transactions. */ if (all_dead && *all_dead && ! !HeapTupleIsSurelyDead(heapTuple, RecentGlobalXmin)) *all_dead = false; /* *************** *** 1781,1786 **** heap_get_latest_tid(Relation relation, --- 1782,1788 ---- tp.t_self = ctid; tp.t_data = (HeapTupleHeader) PageGetItem(page, lp); tp.t_len = ItemIdGetLength(lp); + tp.t_tableOid = RelationGetRelid(relation); /* * After following a t_ctid link, we might arrive at an unrelated *************** *** 2447,2458 **** heap_delete(Relation relation, ItemPointer tid, lp = PageGetItemId(page, ItemPointerGetOffsetNumber(tid)); Assert(ItemIdIsNormal(lp)); tp.t_data = (HeapTupleHeader) PageGetItem(page, lp); tp.t_len = ItemIdGetLength(lp); tp.t_self = *tid; l1: ! result = HeapTupleSatisfiesUpdate(tp.t_data, cid, buffer); if (result == HeapTupleInvisible) { --- 2449,2461 ---- lp = PageGetItemId(page, ItemPointerGetOffsetNumber(tid)); Assert(ItemIdIsNormal(lp)); + tp.t_tableOid = RelationGetRelid(relation); tp.t_data = (HeapTupleHeader) PageGetItem(page, lp); tp.t_len = ItemIdGetLength(lp); tp.t_self = *tid; l1: ! result = HeapTupleSatisfiesUpdate(&tp, cid, buffer); if (result == HeapTupleInvisible) { *************** *** 2817,2822 **** heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, --- 2820,2826 ---- lp = PageGetItemId(page, ItemPointerGetOffsetNumber(otid)); Assert(ItemIdIsNormal(lp)); + oldtup.t_tableOid = RelationGetRelid(relation); oldtup.t_data = (HeapTupleHeader) PageGetItem(page, lp); oldtup.t_len = ItemIdGetLength(lp); oldtup.t_self = *otid; *************** *** 2829,2835 **** heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, */ l2: ! result = HeapTupleSatisfiesUpdate(oldtup.t_data, cid, buffer); if (result == HeapTupleInvisible) { --- 2833,2839 ---- */ l2: ! result = HeapTupleSatisfiesUpdate(&oldtup, cid, buffer); if (result == HeapTupleInvisible) { *************** *** 3531,3537 **** heap_lock_tuple(Relation relation, HeapTuple tuple, tuple->t_tableOid = RelationGetRelid(relation); l3: ! result = HeapTupleSatisfiesUpdate(tuple->t_data, cid, *buffer); if (result == HeapTupleInvisible) { --- 3535,3541 ---- tuple->t_tableOid = RelationGetRelid(relation); l3: ! result = HeapTupleSatisfiesUpdate(tuple, cid, *buffer); if (result == HeapTupleInvisible) { *** a/src/backend/access/heap/pruneheap.c --- b/src/backend/access/heap/pruneheap.c *************** *** 340,345 **** heap_prune_chain(Relation relation, Buffer buffer, OffsetNumber rootoffnum, --- 340,348 ---- OffsetNumber chainitems[MaxHeapTuplesPerPage]; int nchain = 0, i; + HeapTupleData tup; + + tup.t_tableOid = RelationGetRelid(relation); rootlp = PageGetItemId(dp, rootoffnum); *************** *** 349,356 **** heap_prune_chain(Relation relation, Buffer buffer, OffsetNumber rootoffnum, --- 352,365 ---- if (ItemIdIsNormal(rootlp)) { htup = (HeapTupleHeader) PageGetItem(dp, rootlp); + if (HeapTupleHeaderIsHeapOnly(htup)) { + /* fill in the rest of the tuple */ + tup.t_data = htup; + tup.t_len = ItemIdGetLength(rootlp); + ItemPointerSet(&(tup.t_self), BufferGetBlockNumber(buffer), rootoffnum); + /* * If the tuple is DEAD and doesn't chain to anything else, mark * it unused immediately. (If it does chain, we can only remove *************** *** 369,375 **** heap_prune_chain(Relation relation, Buffer buffer, OffsetNumber rootoffnum, * either here or while following a chain below. Whichever path * gets there first will mark the tuple unused. */ ! if (HeapTupleSatisfiesVacuum(htup, OldestXmin, buffer) == HEAPTUPLE_DEAD && !HeapTupleHeaderIsHotUpdated(htup)) { heap_prune_record_unused(prstate, rootoffnum); --- 378,384 ---- * either here or while following a chain below. Whichever path * gets there first will mark the tuple unused. */ ! if (HeapTupleSatisfiesVacuum(&tup, OldestXmin, buffer) == HEAPTUPLE_DEAD && !HeapTupleHeaderIsHotUpdated(htup)) { heap_prune_record_unused(prstate, rootoffnum); *************** *** 448,455 **** heap_prune_chain(Relation relation, Buffer buffer, OffsetNumber rootoffnum, * Check tuple's visibility status. */ tupdead = recent_dead = false; ! switch (HeapTupleSatisfiesVacuum(htup, OldestXmin, buffer)) { case HEAPTUPLE_DEAD: tupdead = true; --- 457,467 ---- * Check tuple's visibility status. */ tupdead = recent_dead = false; + tup.t_data = htup; + tup.t_len = ItemIdGetLength(lp); + ItemPointerSet(&(tup.t_self), BufferGetBlockNumber(buffer), offnum); ! switch (HeapTupleSatisfiesVacuum(&tup, OldestXmin, buffer)) { case HEAPTUPLE_DEAD: tupdead = true; *** a/src/backend/catalog/index.c --- b/src/backend/catalog/index.c *************** *** 2269,2275 **** IndexBuildHeapScan(Relation heapRelation, */ LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE); ! switch (HeapTupleSatisfiesVacuum(heapTuple->t_data, OldestXmin, scan->rs_cbuf)) { case HEAPTUPLE_DEAD: --- 2269,2275 ---- */ LockBuffer(scan->rs_cbuf, BUFFER_LOCK_SHARE); ! switch (HeapTupleSatisfiesVacuum(heapTuple, OldestXmin, scan->rs_cbuf)) { case HEAPTUPLE_DEAD: *** a/src/backend/commands/analyze.c --- b/src/backend/commands/analyze.c *************** *** 1134,1143 **** acquire_sample_rows(Relation onerel, int elevel, ItemPointerSet(&targtuple.t_self, targblock, targoffset); targtuple.t_data = (HeapTupleHeader) PageGetItem(targpage, itemid); targtuple.t_len = ItemIdGetLength(itemid); ! switch (HeapTupleSatisfiesVacuum(targtuple.t_data, OldestXmin, targbuffer)) { --- 1134,1144 ---- ItemPointerSet(&targtuple.t_self, targblock, targoffset); + targtuple.t_tableOid = RelationGetRelid(onerel); targtuple.t_data = (HeapTupleHeader) PageGetItem(targpage, itemid); targtuple.t_len = ItemIdGetLength(itemid); ! switch (HeapTupleSatisfiesVacuum(&targtuple, OldestXmin, targbuffer)) { *** a/src/backend/commands/cluster.c --- b/src/backend/commands/cluster.c *************** *** 931,937 **** copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, LockBuffer(buf, BUFFER_LOCK_SHARE); ! switch (HeapTupleSatisfiesVacuum(tuple->t_data, OldestXmin, buf)) { case HEAPTUPLE_DEAD: /* Definitely dead */ --- 931,937 ---- LockBuffer(buf, BUFFER_LOCK_SHARE); ! switch (HeapTupleSatisfiesVacuum(tuple, OldestXmin, buf)) { case HEAPTUPLE_DEAD: /* Definitely dead */ *** a/src/backend/commands/vacuumlazy.c --- b/src/backend/commands/vacuumlazy.c *************** *** 727,738 **** lazy_scan_heap(Relation onerel, LVRelStats *vacrelstats, Assert(ItemIdIsNormal(itemid)); tuple.t_data = (HeapTupleHeader) PageGetItem(page, itemid); tuple.t_len = ItemIdGetLength(itemid); tupgone = false; ! switch (HeapTupleSatisfiesVacuum(tuple.t_data, OldestXmin, buf)) { case HEAPTUPLE_DEAD: --- 727,739 ---- Assert(ItemIdIsNormal(itemid)); + tuple.t_tableOid = RelationGetRelid(onerel); tuple.t_data = (HeapTupleHeader) PageGetItem(page, itemid); tuple.t_len = ItemIdGetLength(itemid); tupgone = false; ! switch (HeapTupleSatisfiesVacuum(&tuple, OldestXmin, buf)) { case HEAPTUPLE_DEAD: *** a/src/backend/storage/lmgr/predicate.c --- b/src/backend/storage/lmgr/predicate.c *************** *** 3894,3900 **** CheckForSerializableConflictOut(bool visible, Relation relation, * tuple is visible to us, while HeapTupleSatisfiesVacuum checks what else * is going on with it. */ ! htsvResult = HeapTupleSatisfiesVacuum(tuple->t_data, TransactionXmin, buffer); switch (htsvResult) { case HEAPTUPLE_LIVE: --- 3894,3900 ---- * tuple is visible to us, while HeapTupleSatisfiesVacuum checks what else * is going on with it. */ ! htsvResult = HeapTupleSatisfiesVacuum(tuple, TransactionXmin, buffer); switch (htsvResult) { case HEAPTUPLE_LIVE: *** a/src/backend/utils/time/tqual.c --- b/src/backend/utils/time/tqual.c *************** *** 163,170 **** HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer, * Xmax is not committed))) that has not been committed */ bool ! HeapTupleSatisfiesSelf(HeapTupleHeader tuple, Snapshot snapshot, Buffer buffer) { if (!(tuple->t_infomask & HEAP_XMIN_COMMITTED)) { if (tuple->t_infomask & HEAP_XMIN_INVALID) --- 163,174 ---- * Xmax is not committed))) that has not been committed */ bool ! HeapTupleSatisfiesSelf(HeapTuple htup, Snapshot snapshot, Buffer buffer) { + HeapTupleHeader tuple = htup->t_data; + Assert(ItemPointerIsValid(&htup->t_self)); + Assert(htup->t_tableOid != InvalidOid); + if (!(tuple->t_infomask & HEAP_XMIN_COMMITTED)) { if (tuple->t_infomask & HEAP_XMIN_INVALID) *************** *** 326,333 **** HeapTupleSatisfiesSelf(HeapTupleHeader tuple, Snapshot snapshot, Buffer buffer) * */ bool ! HeapTupleSatisfiesNow(HeapTupleHeader tuple, Snapshot snapshot, Buffer buffer) { if (!(tuple->t_infomask & HEAP_XMIN_COMMITTED)) { if (tuple->t_infomask & HEAP_XMIN_INVALID) --- 330,341 ---- * */ bool ! HeapTupleSatisfiesNow(HeapTuple htup, Snapshot snapshot, Buffer buffer) { + HeapTupleHeader tuple = htup->t_data; + Assert(ItemPointerIsValid(&htup->t_self)); + Assert(htup->t_tableOid != InvalidOid); + if (!(tuple->t_infomask & HEAP_XMIN_COMMITTED)) { if (tuple->t_infomask & HEAP_XMIN_INVALID) *************** *** 471,477 **** HeapTupleSatisfiesNow(HeapTupleHeader tuple, Snapshot snapshot, Buffer buffer) * Dummy "satisfies" routine: any tuple satisfies SnapshotAny. */ bool ! HeapTupleSatisfiesAny(HeapTupleHeader tuple, Snapshot snapshot, Buffer buffer) { return true; } --- 479,485 ---- * Dummy "satisfies" routine: any tuple satisfies SnapshotAny. */ bool ! HeapTupleSatisfiesAny(HeapTuple htup, Snapshot snapshot, Buffer buffer) { return true; } *************** *** 491,499 **** HeapTupleSatisfiesAny(HeapTupleHeader tuple, Snapshot snapshot, Buffer buffer) * table. */ bool ! HeapTupleSatisfiesToast(HeapTupleHeader tuple, Snapshot snapshot, Buffer buffer) { if (!(tuple->t_infomask & HEAP_XMIN_COMMITTED)) { if (tuple->t_infomask & HEAP_XMIN_INVALID) --- 499,511 ---- * table. */ bool ! HeapTupleSatisfiesToast(HeapTuple htup, Snapshot snapshot, Buffer buffer) { + HeapTupleHeader tuple = htup->t_data; + Assert(ItemPointerIsValid(&htup->t_self)); + Assert(htup->t_tableOid != InvalidOid); + if (!(tuple->t_infomask & HEAP_XMIN_COMMITTED)) { if (tuple->t_infomask & HEAP_XMIN_INVALID) *************** *** 572,580 **** HeapTupleSatisfiesToast(HeapTupleHeader tuple, Snapshot snapshot, * distinguish that case must test for it themselves.) */ HTSU_Result ! HeapTupleSatisfiesUpdate(HeapTupleHeader tuple, CommandId curcid, Buffer buffer) { if (!(tuple->t_infomask & HEAP_XMIN_COMMITTED)) { if (tuple->t_infomask & HEAP_XMIN_INVALID) --- 584,596 ---- * distinguish that case must test for it themselves.) */ HTSU_Result ! HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid, Buffer buffer) { + HeapTupleHeader tuple = htup->t_data; + Assert(ItemPointerIsValid(&htup->t_self)); + Assert(htup->t_tableOid != InvalidOid); + if (!(tuple->t_infomask & HEAP_XMIN_COMMITTED)) { if (tuple->t_infomask & HEAP_XMIN_INVALID) *************** *** 739,747 **** HeapTupleSatisfiesUpdate(HeapTupleHeader tuple, CommandId curcid, * for snapshot->xmax and the tuple's xmax. */ bool ! HeapTupleSatisfiesDirty(HeapTupleHeader tuple, Snapshot snapshot, Buffer buffer) { snapshot->xmin = snapshot->xmax = InvalidTransactionId; if (!(tuple->t_infomask & HEAP_XMIN_COMMITTED)) --- 755,767 ---- * for snapshot->xmax and the tuple's xmax. */ bool ! HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot, Buffer buffer) { + HeapTupleHeader tuple = htup->t_data; + Assert(ItemPointerIsValid(&htup->t_self)); + Assert(htup->t_tableOid != InvalidOid); + snapshot->xmin = snapshot->xmax = InvalidTransactionId; if (!(tuple->t_infomask & HEAP_XMIN_COMMITTED)) *************** *** 902,910 **** HeapTupleSatisfiesDirty(HeapTupleHeader tuple, Snapshot snapshot, * can't see it.) */ bool ! HeapTupleSatisfiesMVCC(HeapTupleHeader tuple, Snapshot snapshot, Buffer buffer) { if (!(tuple->t_infomask & HEAP_XMIN_COMMITTED)) { if (tuple->t_infomask & HEAP_XMIN_INVALID) --- 922,935 ---- * can't see it.) */ bool ! HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot, Buffer buffer) { + HeapTupleHeader tuple = htup->t_data; + + Assert(ItemPointerIsValid(&htup->t_self)); + Assert(htup->t_tableOid != InvalidOid); + if (!(tuple->t_infomask & HEAP_XMIN_COMMITTED)) { if (tuple->t_infomask & HEAP_XMIN_INVALID) *************** *** 1058,1066 **** HeapTupleSatisfiesMVCC(HeapTupleHeader tuple, Snapshot snapshot, * even if we see that the deleting transaction has committed. */ HTSV_Result ! HeapTupleSatisfiesVacuum(HeapTupleHeader tuple, TransactionId OldestXmin, Buffer buffer) { /* * Has inserting transaction committed? * --- 1083,1095 ---- * even if we see that the deleting transaction has committed. */ HTSV_Result ! HeapTupleSatisfiesVacuum(HeapTuple htup, TransactionId OldestXmin, Buffer buffer) { + HeapTupleHeader tuple = htup->t_data; + Assert(ItemPointerIsValid(&htup->t_self)); + Assert(htup->t_tableOid != InvalidOid); + /* * Has inserting transaction committed? * *************** *** 1233,1240 **** HeapTupleSatisfiesVacuum(HeapTupleHeader tuple, TransactionId OldestXmin, * just whether or not the tuple is surely dead). */ bool ! HeapTupleIsSurelyDead(HeapTupleHeader tuple, TransactionId OldestXmin) { /* * If the inserting transaction is marked invalid, then it aborted, and * the tuple is definitely dead. If it's marked neither committed nor --- 1262,1273 ---- * just whether or not the tuple is surely dead). */ bool ! HeapTupleIsSurelyDead(HeapTuple htup, TransactionId OldestXmin) { + HeapTupleHeader tuple = htup->t_data; + Assert(ItemPointerIsValid(&htup->t_self)); + Assert(htup->t_tableOid != InvalidOid); + /* * If the inserting transaction is marked invalid, then it aborted, and * the tuple is definitely dead. If it's marked neither committed nor *** a/src/include/utils/snapshot.h --- b/src/include/utils/snapshot.h *************** *** 27,34 **** typedef struct SnapshotData *Snapshot; * The specific semantics of a snapshot are encoded by the "satisfies" * function. */ ! typedef bool (*SnapshotSatisfiesFunc) (HeapTupleHeader tuple, ! Snapshot snapshot, Buffer buffer); typedef struct SnapshotData { --- 27,34 ---- * The specific semantics of a snapshot are encoded by the "satisfies" * function. */ ! typedef bool (*SnapshotSatisfiesFunc) (HeapTuple htup, ! Snapshot snapshot, Buffer buffer); typedef struct SnapshotData { *** a/src/include/utils/tqual.h --- b/src/include/utils/tqual.h *************** *** 52,58 **** extern PGDLLIMPORT SnapshotData SnapshotToastData; * if so, the indicated buffer is marked dirty. */ #define HeapTupleSatisfiesVisibility(tuple, snapshot, buffer) \ ! ((*(snapshot)->satisfies) ((tuple)->t_data, snapshot, buffer)) /* Result codes for HeapTupleSatisfiesVacuum */ typedef enum --- 52,58 ---- * if so, the indicated buffer is marked dirty. */ #define HeapTupleSatisfiesVisibility(tuple, snapshot, buffer) \ ! ((*(snapshot)->satisfies) (tuple, snapshot, buffer)) /* Result codes for HeapTupleSatisfiesVacuum */ typedef enum *************** *** 65,89 **** typedef enum } HTSV_Result; /* These are the "satisfies" test routines for the various snapshot types */ ! extern bool HeapTupleSatisfiesMVCC(HeapTupleHeader tuple, Snapshot snapshot, Buffer buffer); ! extern bool HeapTupleSatisfiesNow(HeapTupleHeader tuple, Snapshot snapshot, Buffer buffer); ! extern bool HeapTupleSatisfiesSelf(HeapTupleHeader tuple, Snapshot snapshot, Buffer buffer); ! extern bool HeapTupleSatisfiesAny(HeapTupleHeader tuple, Snapshot snapshot, Buffer buffer); ! extern bool HeapTupleSatisfiesToast(HeapTupleHeader tuple, Snapshot snapshot, Buffer buffer); ! extern bool HeapTupleSatisfiesDirty(HeapTupleHeader tuple, Snapshot snapshot, Buffer buffer); /* Special "satisfies" routines with different APIs */ ! extern HTSU_Result HeapTupleSatisfiesUpdate(HeapTupleHeader tuple, CommandId curcid, Buffer buffer); ! extern HTSV_Result HeapTupleSatisfiesVacuum(HeapTupleHeader tuple, TransactionId OldestXmin, Buffer buffer); ! extern bool HeapTupleIsSurelyDead(HeapTupleHeader tuple, TransactionId OldestXmin); extern void HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer, --- 65,89 ---- } HTSV_Result; /* These are the "satisfies" test routines for the various snapshot types */ ! extern bool HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot, Buffer buffer); ! extern bool HeapTupleSatisfiesNow(HeapTuple htup, Snapshot snapshot, Buffer buffer); ! extern bool HeapTupleSatisfiesSelf(HeapTuple htup, Snapshot snapshot, Buffer buffer); ! extern bool HeapTupleSatisfiesAny(HeapTuple htup, Snapshot snapshot, Buffer buffer); ! extern bool HeapTupleSatisfiesToast(HeapTuple htup, Snapshot snapshot, Buffer buffer); ! extern bool HeapTupleSatisfiesDirty(HeapTuple htup, Snapshot snapshot, Buffer buffer); /* Special "satisfies" routines with different APIs */ ! extern HTSU_Result HeapTupleSatisfiesUpdate(HeapTuple htup, CommandId curcid, Buffer buffer); ! extern HTSV_Result HeapTupleSatisfiesVacuum(HeapTuple htup, TransactionId OldestXmin, Buffer buffer); ! extern bool HeapTupleIsSurelyDead(HeapTuple htup, TransactionId OldestXmin); extern void HeapTupleSetHintBits(HeapTupleHeader tuple, Buffer buffer,
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers