Here's a patch that implements the Phantom Command ID idea that has been
discussed.
I didn't do anything about the system columns. We need to before
applying the patch, because the HeapTupleHeaderGetCmin/max functions
don't work properly if called outside the inserting/deleting
transaction. In fact, SELECT cmin,cmax FROM foo will cause assertion
failures if there's rows with phantom cids in the table.
I used the last free bit in t_infomask. At first I thought it wouldn't
be necessary, because you could detect that a row has a phantom command
id if both xmin and xmax are part of the current top-level transaction
(TransactionIdIsCurrentTransactionId). But that doesn't work because we
don't consider aborted subtransactions as current. Using the infomask
bit seems more robust anyway.
--
Heikki Linnakangas
EnterpriseDB http://www.enterprisedb.com
Index: src/backend/access/heap/heapam.c
===================================================================
RCS file: /home/hlinnaka/pgcvsrepository/pgsql/src/backend/access/heap/heapam.c,v
retrieving revision 1.219
diff -c -r1.219 heapam.c
*** src/backend/access/heap/heapam.c 18 Aug 2006 16:09:08 -0000 1.219
--- src/backend/access/heap/heapam.c 26 Sep 2006 11:44:22 -0000
***************
*** 1405,1412 ****
tup->t_data->t_infomask |= HEAP_XMAX_INVALID;
HeapTupleHeaderSetXmin(tup->t_data, xid);
HeapTupleHeaderSetCmin(tup->t_data, cid);
! HeapTupleHeaderSetXmax(tup->t_data, 0); /* zero out Datum fields */
! HeapTupleHeaderSetCmax(tup->t_data, 0); /* for cleanliness */
tup->t_tableOid = RelationGetRelid(relation);
/*
--- 1405,1411 ----
tup->t_data->t_infomask |= HEAP_XMAX_INVALID;
HeapTupleHeaderSetXmin(tup->t_data, xid);
HeapTupleHeaderSetCmin(tup->t_data, cid);
! HeapTupleHeaderSetXmax(tup->t_data, 0);
tup->t_tableOid = RelationGetRelid(relation);
/*
***************
*** 2045,2052 ****
newtup->t_data->t_infomask |= (HEAP_XMAX_INVALID | HEAP_UPDATED);
HeapTupleHeaderSetXmin(newtup->t_data, xid);
HeapTupleHeaderSetCmin(newtup->t_data, cid);
! HeapTupleHeaderSetXmax(newtup->t_data, 0); /* zero out Datum fields */
! HeapTupleHeaderSetCmax(newtup->t_data, 0); /* for cleanliness */
/*
* If the toaster needs to be activated, OR if the new tuple will not fit
--- 2044,2050 ----
newtup->t_data->t_infomask |= (HEAP_XMAX_INVALID | HEAP_UPDATED);
HeapTupleHeaderSetXmin(newtup->t_data, xid);
HeapTupleHeaderSetCmin(newtup->t_data, cid);
! HeapTupleHeaderSetXmax(newtup->t_data, 0);
/*
* If the toaster needs to be activated, OR if the new tuple will not fit
Index: src/backend/access/transam/xact.c
===================================================================
RCS file: /home/hlinnaka/pgcvsrepository/pgsql/src/backend/access/transam/xact.c,v
retrieving revision 1.226
diff -c -r1.226 xact.c
*** src/backend/access/transam/xact.c 27 Aug 2006 19:11:46 -0000 1.226
--- src/backend/access/transam/xact.c 27 Sep 2006 11:03:25 -0000
***************
*** 43,48 ****
--- 43,49 ----
#include "utils/memutils.h"
#include "utils/relcache.h"
#include "utils/guc.h"
+ #include "utils/phantomcid.h"
/*
***************
*** 1595,1600 ****
--- 1596,1602 ----
AtEOXact_Namespace(true);
/* smgrcommit already done */
AtEOXact_Files();
+ AtEOXact_PhantomCid();
pgstat_count_xact_commit();
CurrentResourceOwner = NULL;
***************
*** 1810,1815 ****
--- 1812,1818 ----
AtEOXact_Namespace(true);
/* smgrcommit already done */
AtEOXact_Files();
+ AtEOXact_PhantomCid();
CurrentResourceOwner = NULL;
ResourceOwnerDelete(TopTransactionResourceOwner);
***************
*** 1961,1966 ****
--- 1964,1970 ----
AtEOXact_Namespace(false);
smgrabort();
AtEOXact_Files();
+ AtEOXact_PhantomCid();
pgstat_count_xact_rollback();
/*
Index: src/backend/utils/time/Makefile
===================================================================
RCS file: /home/hlinnaka/pgcvsrepository/pgsql/src/backend/utils/time/Makefile,v
retrieving revision 1.10
diff -c -r1.10 Makefile
*** src/backend/utils/time/Makefile 29 Nov 2003 19:52:04 -0000 1.10
--- src/backend/utils/time/Makefile 20 Sep 2006 10:05:19 -0000
***************
*** 12,18 ****
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
! OBJS = tqual.o
all: SUBSYS.o
--- 12,18 ----
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
! OBJS = tqual.o phantomcid.o
all: SUBSYS.o
Index: src/backend/utils/time/phantomcid.c
===================================================================
RCS file: src/backend/utils/time/phantomcid.c
diff -N src/backend/utils/time/phantomcid.c
*** /dev/null 1 Jan 1970 00:00:00 -0000
--- src/backend/utils/time/phantomcid.c 28 Sep 2006 09:43:13 -0000
***************
*** 0 ****
--- 1,274 ----
+ /*-------------------------------------------------------------------------
+ *
+ * phantomcid.c
+ * Phantom command id support routines
+ *
+ * Before version 8.3, HeapTupleHeaderData had separate fields for cmin
+ * and cmax. To reduce the header size, cmin and cmax are now overlayed
+ * in the same field in the header. That usually works because you rarely
+ * insert and delete a tuple in the transaction. To make it work when you
+ * do, we create a phantom command id and store that in the tuple header
+ * instead of cmin and cmax. The phantom command id maps to the real cmin
+ * and cmax in a backend-private array. Other backends don't need them,
+ * because cmin and cmax are only interesting to the inserting/deleting
+ * transaction.
+ *
+ * To allow reusing existing phantom cids, we also keep a hash table that
+ * maps cmin,cmax pairs to phantom cids.
+ *
+ * The array and hash table are kept in TopTransactionContext, and are
+ * destroyed at the end of transaction.
+ *
+ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * $PostgreSQL$
+ *
+ *-------------------------------------------------------------------------
+ */
+
+ #include "postgres.h"
+
+ #include "access/htup.h"
+ #include "access/xact.h"
+ #include "utils/memutils.h"
+ #include "utils/hsearch.h"
+
+ #define PHANTOMCID_DEBUG
+
+
+ /* Hash table to lookup phantom cids by cmin and cmax */
+ static HTAB *phantomHash = NULL;
+
+ /* Key and entry structures for the hash table */
+ typedef struct {
+ CommandId cmin;
+ CommandId cmax;
+ } PhantomCidKeyData;
+
+ typedef struct {
+ PhantomCidKeyData key;
+ CommandId phantomcid;
+ } PhantomCidEntryData;
+
+ typedef PhantomCidKeyData *PhantomCidKey;
+ typedef PhantomCidEntryData *PhantomCidEntry;
+
+ #define PCID_HASH_SIZE 100
+
+
+ /* An array of cmin,cmax pairs, indexed by phantom command id.
+ * To convert a phantom cid to cmin and cmax, you do a simple array
+ * lookup. */
+ static PhantomCidKey phantomCids = NULL;
+ static int usedPhantomCids = 0; /* number of elements in phantomCids */
+ static int sizePhantomCids = 0; /* size of phantomCids array */
+
+ /* Initial size of the array. It will be grown if it fills up */
+ #define PCID_ARRAY_INITIAL_SIZE 100
+
+
+ /* prototypes for internal functions */
+ static CommandId GetPhantomCommandId(CommandId cmin, CommandId cmax);
+ static CommandId GetRealCmin(CommandId phantomcid);
+ static CommandId GetRealCmax(CommandId phantomcid);
+
+ /**** External API ****/
+
+ /* All these functions rely on the caller to not call functions that don't make
+ * sense. You should only call cmin related functions in the inserting
+ * transaction and cmax related functions in the deleting transaction.
+ */
+
+ CommandId
+ HeapTupleHeaderGetCmin(HeapTupleHeader tup)
+ {
+ CommandId cid = tup->t_choice.t_heap.t_field4.t_commandid;
+
+ Assert(!(tup->t_infomask & HEAP_MOVED));
+
+ if (tup->t_infomask & HEAP_PHANTOMCID)
+ return GetRealCmin(cid);
+ else
+ return cid;
+ }
+
+ void
+ HeapTupleHeaderSetCmin(HeapTupleHeader tup, CommandId cmin)
+ {
+ /* We never need to create a phantom cid for a new tuple. */
+ tup->t_choice.t_heap.t_field4.t_commandid = cmin;
+ }
+
+ CommandId
+ HeapTupleHeaderGetCmax(HeapTupleHeader tup)
+ {
+ CommandId cid = tup->t_choice.t_heap.t_field4.t_commandid;
+
+ Assert(!(tup->t_infomask & HEAP_MOVED));
+
+ if (tup->t_infomask & HEAP_PHANTOMCID)
+ return GetRealCmax(cid);
+ else
+ return cid;
+ }
+
+ void
+ HeapTupleHeaderSetCmax(HeapTupleHeader tup, CommandId cmax)
+ {
+ HeapTupleFields *t_heap = &tup->t_choice.t_heap;
+
+ if (!(tup->t_infomask & HEAP_XMIN_COMMITTED)
+ && TransactionIdIsCurrentTransactionId(t_heap->t_xmin))
+ {
+ /* This row was inserted by our transaction, and now we're
+ * deleting it. Need to use phantom cid. */
+ CommandId cmin = t_heap->t_field4.t_commandid;
+ t_heap->t_field4.t_commandid = GetPhantomCommandId(cmin, cmax);
+ tup->t_infomask |= HEAP_PHANTOMCID;
+ }
+ else {
+ t_heap->t_field4.t_commandid = cmax;
+ tup->t_infomask &= ~HEAP_PHANTOMCID;
+ }
+ }
+ /*
+ * Phantom command IDs are only interesting to the inserting and deleting
+ * transaction, so we can forget about them at the end of transaction.
+ */
+ void
+ AtEOXact_PhantomCid()
+ {
+ usedPhantomCids = 0;
+ sizePhantomCids = 0;
+ /* Don't bother to pfree. These are allocated in TopTransactionContext,
+ * so they're going to be freed at the end of transaction anyway.
+ */
+ phantomCids = NULL;
+ phantomHash = NULL;
+ }
+
+
+ /**** Internal routines ****/
+
+ /*
+ * Get a phantom command id that maps to cmin and cmax.
+ *
+ * We try to reuse old phantom command ids when possible.
+ */
+ static
+ CommandId GetPhantomCommandId(CommandId cmin, CommandId cmax)
+ {
+ CommandId phantomcid;
+ PhantomCidKeyData key;
+ PhantomCidEntry entry = NULL;
+ bool found;
+
+ #ifdef PHANTOMCID_DEBUG
+ elog(LOG, "GetPhantomCommandId cmin: %d cmax: %d", cmin, cmax);
+ #endif
+
+ /* Create the array and hash table the first time we need to use
+ * phantom cids in the transaction.
+ */
+ if(phantomCids == NULL)
+ {
+ HASHCTL hash_ctl;
+ MemoryContext oldcontext;
+
+ oldcontext = MemoryContextSwitchTo(TopTransactionContext);
+
+ phantomCids =
+ palloc(sizeof(PhantomCidKeyData) * PCID_ARRAY_INITIAL_SIZE);
+ sizePhantomCids = PCID_ARRAY_INITIAL_SIZE;
+
+ memset(&hash_ctl, 0, sizeof(hash_ctl));
+ hash_ctl.keysize = sizeof(PhantomCidKeyData);
+ hash_ctl.entrysize = sizeof(PhantomCidEntryData);
+ hash_ctl.hash = tag_hash;
+ hash_ctl.hcxt = TopTransactionContext;
+
+ phantomHash =
+ hash_create("Phantom command id hash table",
+ PCID_HASH_SIZE, &hash_ctl,
+ HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT);
+
+ MemoryContextSwitchTo(TopTransactionContext);
+ }
+
+ /* Try to find an old phantom cid with the same cmin and cmax for reuse */
+
+ key.cmin = cmin;
+ key.cmax = cmax;
+ entry = (PhantomCidEntry)
+ hash_search(phantomHash, (void *) &key, HASH_ENTER, &found);
+
+ if (found)
+ {
+ #ifdef PHANTOMCID_DEBUG
+ elog(LOG, "result: %d (reused)", entry->phantomcid);
+ #endif
+ return entry->phantomcid;
+ }
+ else
+ {
+ /* We have to create a new phantom cid. Check that there's room
+ * for it in the array, and grow it if there isn't */
+ if (usedPhantomCids >= sizePhantomCids)
+ {
+ /* We need to grow the array */
+
+ MemoryContext oldcontext;
+ oldcontext = MemoryContextSwitchTo(TopTransactionContext);
+
+ /* XXX: Should we create a bigger hash table too? */
+ sizePhantomCids *= 2;
+ phantomCids =
+ repalloc(phantomCids,
+ sizeof(PhantomCidKeyData) * sizePhantomCids);
+
+ MemoryContextSwitchTo(oldcontext);
+ }
+
+ phantomcid = usedPhantomCids;
+ entry->phantomcid = phantomcid;
+
+ phantomCids[phantomcid].cmin = cmin;
+ phantomCids[phantomcid].cmax = cmax;
+
+ usedPhantomCids++;
+
+ #ifdef PHANTOMCID_DEBUG
+ elog(LOG, "result: %d", phantomcid);
+ #endif
+ }
+
+ return phantomcid;
+ }
+
+ static CommandId
+ GetRealCmin(CommandId phantomcid)
+ {
+ Assert(phantomcid < usedPhantomCids);
+
+ #ifdef PHANTOMCID_DEBUG
+ elog(LOG, "GetRealCmin phantomcid: %d -> %d", phantomcid, phantomCids[phantomcid].cmin);
+ #endif
+
+
+ return phantomCids[phantomcid].cmin;
+ }
+
+ static CommandId
+ GetRealCmax(CommandId phantomcid)
+ {
+ Assert(phantomcid < usedPhantomCids);
+
+ #ifdef PHANTOMCID_DEBUG
+ elog(LOG, "GetRealCmax phantomcid: %d -> %d", phantomcid, phantomCids[phantomcid].cmax);
+ #endif
+
+ return phantomCids[phantomcid].cmax;
+ }
+
Index: src/include/access/htup.h
===================================================================
RCS file: /home/hlinnaka/pgcvsrepository/pgsql/src/include/access/htup.h,v
retrieving revision 1.85
diff -c -r1.85 htup.h
*** src/include/access/htup.h 13 Jul 2006 17:47:01 -0000 1.85
--- src/include/access/htup.h 28 Sep 2006 09:23:03 -0000
***************
*** 65,77 ****
* object ID (if HEAP_HASOID is set in t_infomask)
* user data fields
*
! * We store five "virtual" fields Xmin, Cmin, Xmax, Cmax, and Xvac in four
! * physical fields. Xmin, Cmin and Xmax are always really stored, but
! * Cmax and Xvac share a field. This works because we know that there are
! * only a limited number of states that a tuple can be in, and that Cmax
! * is only interesting for the lifetime of the deleting transaction.
! * This assumes that VACUUM FULL never tries to move a tuple whose Cmax
! * is still interesting (ie, delete-in-progress).
*
* Note that in 7.3 and 7.4 a similar idea was applied to Xmax and Cmin.
* However, with the advent of subtransactions, a tuple may need both Xmax
--- 65,81 ----
* object ID (if HEAP_HASOID is set in t_infomask)
* user data fields
*
! * We store five "virtual" fields Xmin, Cmin, Xmax, Cmax, and Xvac in three
! * physical fields. Xmin and Xmax are always really stored, but Cmin, Cmax
! * and Xvac share a field. This works because we know that there are only
! * a limited number of states that a tuple can be in, and that Cmax and Cmin
! * are only interesting for the lifetime of the deleting or inserting
! * transaction. If a tuple is inserted and deleted in the same transaction,
! * we use a phantom command id that maps to the real cmin and cmax. The
! * mapping is local to the backend. See phantomcid.c for more details.
! *
! * This assumes that VACUUM FULL never tries to move a tuple whose Cmax or
! * Cmin is still interesting (ie, delete- or insert-in-progress).
*
* Note that in 7.3 and 7.4 a similar idea was applied to Xmax and Cmin.
* However, with the advent of subtransactions, a tuple may need both Xmax
***************
*** 103,114 ****
typedef struct HeapTupleFields
{
TransactionId t_xmin; /* inserting xact ID */
- CommandId t_cmin; /* inserting command ID */
TransactionId t_xmax; /* deleting or locking xact ID */
union
{
! CommandId t_cmax; /* deleting or locking command ID */
TransactionId t_xvac; /* VACUUM FULL xact ID */
} t_field4;
} HeapTupleFields;
--- 107,121 ----
typedef struct HeapTupleFields
{
TransactionId t_xmin; /* inserting xact ID */
TransactionId t_xmax; /* deleting or locking xact ID */
union
{
! /* t_commandid is the inserting command ID (cmin), the deleting
! * command ID (cmax), or a phantom cid that maps to cmin and cmax if
! * the tuple was inserted and deleted in the same transaction
! */
! CommandId t_commandid;
TransactionId t_xvac; /* VACUUM FULL xact ID */
} t_field4;
} HeapTupleFields;
***************
*** 163,169 ****
#define HEAP_HASCOMPRESSED 0x0008 /* has compressed stored attribute(s) */
#define HEAP_HASEXTENDED 0x000C /* the two above combined */
#define HEAP_HASOID 0x0010 /* has an object-id field */
! /* 0x0020 is presently unused */
#define HEAP_XMAX_EXCL_LOCK 0x0040 /* xmax is exclusive locker */
#define HEAP_XMAX_SHARED_LOCK 0x0080 /* xmax is shared locker */
/* if either LOCK bit is set, xmax hasn't deleted the tuple, only locked it */
--- 170,176 ----
#define HEAP_HASCOMPRESSED 0x0008 /* has compressed stored attribute(s) */
#define HEAP_HASEXTENDED 0x000C /* the two above combined */
#define HEAP_HASOID 0x0010 /* has an object-id field */
! #define HEAP_PHANTOMCID 0x0020 /* t_commandid is a phantom cid */
#define HEAP_XMAX_EXCL_LOCK 0x0040 /* xmax is exclusive locker */
#define HEAP_XMAX_SHARED_LOCK 0x0080 /* xmax is shared locker */
/* if either LOCK bit is set, xmax hasn't deleted the tuple, only locked it */
***************
*** 210,243 ****
TransactionIdStore((xid), &(tup)->t_choice.t_heap.t_xmax) \
)
! #define HeapTupleHeaderGetCmin(tup) \
! ( \
! (tup)->t_choice.t_heap.t_cmin \
! )
!
! #define HeapTupleHeaderSetCmin(tup, cid) \
! ( \
! (tup)->t_choice.t_heap.t_cmin = (cid) \
! )
!
! /*
! * Note: GetCmax will produce wrong answers after SetXvac has been executed
! * by a transaction other than the inserting one. We could check
! * HEAP_XMAX_INVALID and return FirstCommandId if it's clear, but since that
! * bit will be set again if the deleting transaction aborts, there'd be no
! * real gain in safety from the extra test. So, just rely on the caller not
! * to trust the value unless it's meaningful.
*/
- #define HeapTupleHeaderGetCmax(tup) \
- ( \
- (tup)->t_choice.t_heap.t_field4.t_cmax \
- )
-
- #define HeapTupleHeaderSetCmax(tup, cid) \
- do { \
- Assert(!((tup)->t_infomask & HEAP_MOVED)); \
- (tup)->t_choice.t_heap.t_field4.t_cmax = (cid); \
- } while (0)
#define HeapTupleHeaderGetXvac(tup) \
( \
--- 217,225 ----
TransactionIdStore((xid), &(tup)->t_choice.t_heap.t_xmax) \
)
! /* HeapTupleHeaderGetCmin, HeapTupleHeaderGetCmax, and ..SetCmin and ..SetCmax
! * are defined in phantomcid.h
*/
#define HeapTupleHeaderGetXvac(tup) \
( \
***************
*** 613,616 ****
--- 595,605 ----
#define SizeOfHeapInplace (offsetof(xl_heap_inplace, target) + SizeOfHeapTid)
+ /* prototypes of HeapTupleHeader* functions implemented in
+ * utils/time/phantomcid.c */
+ extern CommandId HeapTupleHeaderGetCmin(HeapTupleHeader tup);
+ extern void HeapTupleHeaderSetCmin(HeapTupleHeader tup, CommandId cmin);
+ extern CommandId HeapTupleHeaderGetCmax(HeapTupleHeader tup);
+ extern void HeapTupleHeaderSetCmax(HeapTupleHeader tup, CommandId cmax);
+
#endif /* HTUP_H */
Index: src/include/utils/phantomcid.h
===================================================================
RCS file: src/include/utils/phantomcid.h
diff -N src/include/utils/phantomcid.h
*** /dev/null 1 Jan 1970 00:00:00 -0000
--- src/include/utils/phantomcid.h 28 Sep 2006 09:12:11 -0000
***************
*** 0 ****
--- 1,25 ----
+ /*-------------------------------------------------------------------------
+ *
+ * phantomcid.h
+ * Phantom cid function definitions
+ *
+ *
+ * Portions Copyright (c) 1996-2006, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * $PostgreSQL$
+ *
+ *-------------------------------------------------------------------------
+ */
+ #ifndef PHANTOMCID_H
+ #define PHANTOMCID_H
+
+ /* HeapTupleHeaderGetCmin, *SetCmin, *GetCmax and *SetCmax
+ * function prototypes are in access/htup.h, because that's
+ * where the macro definitions that the functions replaced
+ * used to be.
+ */
+
+ extern void AtEOXact_PhantomCid(void);
+
+ #endif /* PHANTOMCID_H */
Index: src/test/regress/parallel_schedule
===================================================================
RCS file: /home/hlinnaka/pgcvsrepository/pgsql/src/test/regress/parallel_schedule,v
retrieving revision 1.35
diff -c -r1.35 parallel_schedule
*** src/test/regress/parallel_schedule 30 Aug 2006 23:34:22 -0000 1.35
--- src/test/regress/parallel_schedule 27 Sep 2006 12:53:04 -0000
***************
*** 61,67 ****
# ----------
# The fourth group of parallel test
# ----------
! test: select_into select_distinct select_distinct_on select_implicit select_having subselect union case join aggregates transactions random portals arrays btree_index hash_index update namespace prepared_xacts delete
test: privileges
test: misc
--- 61,67 ----
# ----------
# The fourth group of parallel test
# ----------
! test: select_into select_distinct select_distinct_on select_implicit select_having subselect union case join aggregates transactions random portals arrays btree_index hash_index update namespace prepared_xacts delete phantomcid
test: privileges
test: misc
Index: src/test/regress/serial_schedule
===================================================================
RCS file: /home/hlinnaka/pgcvsrepository/pgsql/src/test/regress/serial_schedule,v
retrieving revision 1.33
diff -c -r1.33 serial_schedule
*** src/test/regress/serial_schedule 30 Aug 2006 23:34:22 -0000 1.33
--- src/test/regress/serial_schedule 27 Sep 2006 12:52:34 -0000
***************
*** 104,106 ****
--- 104,107 ----
test: returning
test: stats
test: tablespace
+ test: phantomcid
Index: src/test/regress/expected/phantomcid.out
===================================================================
RCS file: src/test/regress/expected/phantomcid.out
diff -N src/test/regress/expected/phantomcid.out
*** /dev/null 1 Jan 1970 00:00:00 -0000
--- src/test/regress/expected/phantomcid.out 28 Sep 2006 08:51:09 -0000
***************
*** 0 ****
--- 1,28 ----
+ CREATE TEMP TABLE phantomcidtest (foobar int);
+ BEGIN;
+ INSERT INTO phantomcidtest VALUES (1);
+ SELECT * FROM phantomcidtest;
+ foobar
+ --------
+ 1
+ (1 row)
+
+ DELETE FROM phantomcidtest;
+ SELECT * FROM phantomcidtest;
+ foobar
+ --------
+ (0 rows)
+
+ COMMIT;
+ /* Test phantom cids with portals */
+ BEGIN;
+ INSERT INTO phantomcidtest VALUES (1);
+ DECLARE c CURSOR FOR SELECT * FROM phantomcidtest;
+ DELETE FROM phantomcidtest;
+ FETCH ALL FROM c;
+ foobar
+ --------
+ 1
+ (1 row)
+
+ COMMIT;
Index: src/test/regress/sql/phantomcid.sql
===================================================================
RCS file: src/test/regress/sql/phantomcid.sql
diff -N src/test/regress/sql/phantomcid.sql
*** /dev/null 1 Jan 1970 00:00:00 -0000
--- src/test/regress/sql/phantomcid.sql 27 Sep 2006 12:54:18 -0000
***************
*** 0 ****
--- 1,27 ----
+ CREATE TEMP TABLE phantomcidtest (foobar int);
+
+
+ BEGIN;
+
+ INSERT INTO phantomcidtest VALUES (1);
+
+ SELECT * FROM phantomcidtest;
+
+ DELETE FROM phantomcidtest;
+
+ SELECT * FROM phantomcidtest;
+
+ COMMIT;
+
+ /* Test phantom cids with portals */
+ BEGIN;
+
+ INSERT INTO phantomcidtest VALUES (1);
+
+ DECLARE c CURSOR FOR SELECT * FROM phantomcidtest;
+
+ DELETE FROM phantomcidtest;
+
+ FETCH ALL FROM c;
+
+ COMMIT;
---------------------------(end of broadcast)---------------------------
TIP 5: don't forget to increase your free space map settings