*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/access/common/heaptuple.c,v 1.114 2007/01/09 22:00:59 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/access/common/heaptuple.c,v 1.115 2007/02/09 03:35:33 tgl Exp $
*
*-------------------------------------------------------------------------
*/
case MinTransactionIdAttributeNumber:
result = TransactionIdGetDatum(HeapTupleHeaderGetXmin(tup->t_data));
break;
- case MinCommandIdAttributeNumber:
- result = CommandIdGetDatum(HeapTupleHeaderGetCmin(tup->t_data));
- break;
case MaxTransactionIdAttributeNumber:
result = TransactionIdGetDatum(HeapTupleHeaderGetXmax(tup->t_data));
break;
+ case MinCommandIdAttributeNumber:
case MaxCommandIdAttributeNumber:
- result = CommandIdGetDatum(HeapTupleHeaderGetCmax(tup->t_data));
+ /*
+ * cmin and cmax are now both aliases for the same field,
+ * which can in fact also be a combo command id. XXX perhaps we
+ * should return the "real" cmin or cmax if possible, that is
+ * if we are inside the originating transaction?
+ */
+ result = CommandIdGetDatum(HeapTupleHeaderGetRawCommandId(tup->t_data));
break;
case TableOidAttributeNumber:
result = ObjectIdGetDatum(tup->t_tableOid);
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/access/heap/heapam.c,v 1.227 2007/02/05 04:22:18 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/access/heap/heapam.c,v 1.228 2007/02/09 03:35:33 tgl Exp $
*
*
* INTERFACE ROUTINES
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 */
+ HeapTupleHeaderSetXmax(tup->t_data, 0); /* for cleanliness */
tup->t_tableOid = RelationGetRelid(relation);
/*
PageHeader dp;
Buffer buffer;
bool have_tuple_lock = false;
+ bool iscombo;
Assert(ItemPointerIsValid(tid));
return result;
}
+ /* replace cid with a combo cid if necessary */
+ HeapTupleHeaderAdjustCmax(tp.t_data, &cid, &iscombo);
+
START_CRIT_SECTION();
/* store transaction information of xact deleting the tuple */
HEAP_IS_LOCKED |
HEAP_MOVED);
HeapTupleHeaderSetXmax(tp.t_data, xid);
- HeapTupleHeaderSetCmax(tp.t_data, cid);
+ HeapTupleHeaderSetCmax(tp.t_data, cid, iscombo);
/* Make sure there is no forward chain link in t_ctid */
tp.t_data->t_ctid = tp.t_self;
Size newtupsize,
pagefree;
bool have_tuple_lock = false;
+ bool iscombo;
Assert(ItemPointerIsValid(otid));
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 */
+ HeapTupleHeaderSetXmax(newtup->t_data, 0); /* for cleanliness */
+
+ /*
+ * Replace cid with a combo cid if necessary. Note that we already put
+ * the plain cid into the new tuple.
+ */
+ HeapTupleHeaderAdjustCmax(oldtup.t_data, &cid, &iscombo);
/*
* If the toaster needs to be activated, OR if the new tuple will not fit
HEAP_IS_LOCKED |
HEAP_MOVED);
HeapTupleHeaderSetXmax(oldtup.t_data, xid);
- HeapTupleHeaderSetCmax(oldtup.t_data, cid);
+ HeapTupleHeaderSetCmax(oldtup.t_data, cid, iscombo);
/* temporarily make it look not-updated */
oldtup.t_data->t_ctid = oldtup.t_self;
already_marked = true;
HEAP_IS_LOCKED |
HEAP_MOVED);
HeapTupleHeaderSetXmax(oldtup.t_data, xid);
- HeapTupleHeaderSetCmax(oldtup.t_data, cid);
+ HeapTupleHeaderSetCmax(oldtup.t_data, cid, iscombo);
}
/* record address of new tuple in t_ctid of old one */
/*
* Store transaction information of xact locking the tuple.
*
- * Note: our CID is meaningless if storing a MultiXactId, but no harm in
- * storing it anyway.
+ * Note: Cmax is meaningless in this context, so don't set it; this
+ * avoids possibly generating a useless combo CID.
*/
tuple->t_data->t_infomask = new_infomask;
HeapTupleHeaderSetXmax(tuple->t_data, xid);
- HeapTupleHeaderSetCmax(tuple->t_data, cid);
/* Make sure there is no forward chain link in t_ctid */
tuple->t_data->t_ctid = *tid;
HEAP_IS_LOCKED |
HEAP_MOVED);
HeapTupleHeaderSetXmax(htup, record->xl_xid);
- HeapTupleHeaderSetCmax(htup, FirstCommandId);
+ HeapTupleHeaderSetCmax(htup, FirstCommandId, false);
/* Make sure there is no forward chain link in t_ctid */
htup->t_ctid = xlrec->target.tid;
PageSetLSN(page, lsn);
HEAP_IS_LOCKED |
HEAP_MOVED);
HeapTupleHeaderSetXmax(htup, record->xl_xid);
- HeapTupleHeaderSetCmax(htup, FirstCommandId);
+ HeapTupleHeaderSetCmax(htup, FirstCommandId, false);
/* Set forward chain link in t_ctid */
htup->t_ctid = xlrec->newtid;
}
else
htup->t_infomask |= HEAP_XMAX_EXCL_LOCK;
HeapTupleHeaderSetXmax(htup, xlrec->locking_xid);
- HeapTupleHeaderSetCmax(htup, FirstCommandId);
+ HeapTupleHeaderSetCmax(htup, FirstCommandId, false);
/* Make sure there is no forward chain link in t_ctid */
htup->t_ctid = xlrec->target.tid;
PageSetLSN(page, lsn);
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.233 2007/02/07 23:11:29 tgl Exp $
+ * $PostgreSQL: pgsql/src/backend/access/transam/xact.c,v 1.234 2007/02/09 03:35:33 tgl Exp $
*
*-------------------------------------------------------------------------
*/
#include "storage/lmgr.h"
#include "storage/procarray.h"
#include "storage/smgr.h"
+#include "utils/combocid.h"
#include "utils/flatfiles.h"
+#include "utils/guc.h"
#include "utils/inval.h"
#include "utils/memutils.h"
#include "utils/relcache.h"
-#include "utils/guc.h"
/*
AtEOXact_Namespace(true);
/* smgrcommit already done */
AtEOXact_Files();
+ AtEOXact_ComboCid();
pgstat_clear_snapshot();
pgstat_count_xact_commit();
pgstat_report_txn_timestamp(0);
AtEOXact_Namespace(true);
/* smgrcommit already done */
AtEOXact_Files();
+ AtEOXact_ComboCid();
pgstat_clear_snapshot();
CurrentResourceOwner = NULL;
AtEOXact_Namespace(false);
smgrabort();
AtEOXact_Files();
+ AtEOXact_ComboCid();
pgstat_clear_snapshot();
pgstat_count_xact_rollback();
pgstat_report_txn_timestamp(0);
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/utils/fmgr/fmgr.c,v 1.103 2007/01/05 22:19:43 momjian Exp $
+ * $PostgreSQL: pgsql/src/backend/utils/fmgr/fmgr.c,v 1.104 2007/02/09 03:35:34 tgl Exp $
*
*-------------------------------------------------------------------------
*/
/* fn_oid is the hash key and so must be first! */
Oid fn_oid; /* OID of an external C function */
TransactionId fn_xmin; /* for checking up-to-dateness */
- CommandId fn_cmin;
+ ItemPointerData fn_tid;
PGFunction user_fn; /* the function's address */
const Pg_finfo_record *inforec; /* address of its info record */
} CFuncHashTabEntry;
if (entry == NULL)
return NULL; /* no such entry */
if (entry->fn_xmin == HeapTupleHeaderGetXmin(procedureTuple->t_data) &&
- entry->fn_cmin == HeapTupleHeaderGetCmin(procedureTuple->t_data))
+ ItemPointerEquals(&entry->fn_tid, &procedureTuple->t_self))
return entry; /* OK */
return NULL; /* entry is out of date */
}
&found);
/* OID is already filled in */
entry->fn_xmin = HeapTupleHeaderGetXmin(procedureTuple->t_data);
- entry->fn_cmin = HeapTupleHeaderGetCmin(procedureTuple->t_data);
+ entry->fn_tid = procedureTuple->t_self;
entry->user_fn = user_fn;
entry->inforec = inforec;
}
# Makefile for utils/time
#
# IDENTIFICATION
-# $PostgreSQL: pgsql/src/backend/utils/time/Makefile,v 1.11 2007/01/20 17:16:15 petere Exp $
+# $PostgreSQL: pgsql/src/backend/utils/time/Makefile,v 1.12 2007/02/09 03:35:34 tgl Exp $
#
#-------------------------------------------------------------------------
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
-OBJS = tqual.o
+OBJS = combocid.o tqual.o
all: SUBSYS.o
SUBSYS.o: $(OBJS)
$(LD) $(LDREL) $(LDOUT) SUBSYS.o $(OBJS)
-clean:
+clean:
rm -f SUBSYS.o $(OBJS)
--- /dev/null
+/*-------------------------------------------------------------------------
+ *
+ * combocid.c
+ * Combo 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 same transaction, and we don't need
+ * either field to remain valid after the originating transaction exits.
+ * To make it work when the inserting transaction does delete the tuple,
+ * we create a "combo" command ID and store that in the tuple header
+ * instead of cmin and cmax. The combo command ID can be mapped to the
+ * real cmin and cmax using a backend-private array, which is managed by
+ * this module.
+ *
+ * To allow reusing existing combo cids, we also keep a hash table that
+ * maps cmin,cmax pairs to combo cids. This keeps the data structure size
+ * reasonable in most cases, since the number of unique pairs used by any
+ * one transaction is likely to be small.
+ *
+ * With a 32-bit combo command id we can represent 2^32 distinct cmin,cmax
+ * combinations. In the most perverse case where each command deletes a tuple
+ * generated by every previous command, the number of combo command ids
+ * required for N commands is N*(N+1)/2. That means that in the worst case,
+ * that's enough for 92682 commands. In practice, you'll run out of memory
+ * and/or disk space way before you reach that limit.
+ *
+ * The array and hash table are kept in TopTransactionContext, and are
+ * destroyed at the end of each transaction.
+ *
+ *
+ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * IDENTIFICATION
+ * $PostgreSQL: pgsql/src/backend/utils/time/combocid.c,v 1.1 2007/02/09 03:35:34 tgl Exp $
+ *
+ *-------------------------------------------------------------------------
+ */
+
+#include "postgres.h"
+
+#include "access/htup.h"
+#include "access/xact.h"
+#include "utils/combocid.h"
+#include "utils/hsearch.h"
+#include "utils/memutils.h"
+
+
+/* Hash table to lookup combo cids by cmin and cmax */
+static HTAB *comboHash = NULL;
+
+/* Key and entry structures for the hash table */
+typedef struct
+{
+ CommandId cmin;
+ CommandId cmax;
+} ComboCidKeyData;
+
+typedef ComboCidKeyData *ComboCidKey;
+
+typedef struct
+{
+ ComboCidKeyData key;
+ CommandId combocid;
+} ComboCidEntryData;
+
+typedef ComboCidEntryData *ComboCidEntry;
+
+/* Initial size of the hash table */
+#define CCID_HASH_SIZE 100
+
+
+/*
+ * An array of cmin,cmax pairs, indexed by combo command id.
+ * To convert a combo cid to cmin and cmax, you do a simple array lookup.
+ */
+static ComboCidKey comboCids = NULL;
+static int usedComboCids = 0; /* number of elements in comboCids */
+static int sizeComboCids = 0; /* allocated size of array */
+
+/* Initial size of the array */
+#define CCID_ARRAY_SIZE 100
+
+
+/* prototypes for internal functions */
+static CommandId GetComboCommandId(CommandId cmin, CommandId cmax);
+static CommandId GetRealCmin(CommandId combocid);
+static CommandId GetRealCmax(CommandId combocid);
+
+
+/**** External API ****/
+
+/*
+ * GetCmin and GetCmax assert that they are only called in situations where
+ * they make sense, that is, can deliver a useful answer. If you have
+ * reason to examine a tuple's t_cid field from a transaction other than
+ * the originating one, use HeapTupleHeaderGetRawCommandId() directly.
+ */
+
+CommandId
+HeapTupleHeaderGetCmin(HeapTupleHeader tup)
+{
+ CommandId cid = HeapTupleHeaderGetRawCommandId(tup);
+
+ Assert(!(tup->t_infomask & HEAP_MOVED));
+ Assert(TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tup)));
+
+ if (tup->t_infomask & HEAP_COMBOCID)
+ return GetRealCmin(cid);
+ else
+ return cid;
+}
+
+CommandId
+HeapTupleHeaderGetCmax(HeapTupleHeader tup)
+{
+ CommandId cid = HeapTupleHeaderGetRawCommandId(tup);
+
+ /* We do not store cmax when locking a tuple */
+ Assert(!(tup->t_infomask & (HEAP_MOVED | HEAP_IS_LOCKED)));
+ Assert(TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(tup)));
+
+ if (tup->t_infomask & HEAP_COMBOCID)
+ return GetRealCmax(cid);
+ else
+ return cid;
+}
+
+/*
+ * Given a tuple we are about to delete, determine the correct value to store
+ * into its t_cid field.
+ *
+ * If we don't need a combo CID, *cmax is unchanged and *iscombo is set to
+ * FALSE. If we do need one, *cmax is replaced by a combo CID and *iscombo
+ * is set to TRUE.
+ *
+ * The reason this is separate from the actual HeapTupleHeaderSetCmax()
+ * operation is that this could fail due to out-of-memory conditions. Hence
+ * we need to do this before entering the critical section that actually
+ * changes the tuple in shared buffers.
+ */
+void
+HeapTupleHeaderAdjustCmax(HeapTupleHeader tup,
+ CommandId *cmax,
+ bool *iscombo)
+{
+ /*
+ * If we're marking a tuple deleted that was inserted by (any
+ * subtransaction of) our transaction, we need to use a combo command id.
+ * Test for HEAP_XMIN_COMMITTED first, because it's cheaper than a
+ * TransactionIdIsCurrentTransactionId call.
+ */
+ if (!(tup->t_infomask & HEAP_XMIN_COMMITTED) &&
+ TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tup)))
+ {
+ CommandId cmin = HeapTupleHeaderGetRawCommandId(tup);
+
+ *cmax = GetComboCommandId(cmin, *cmax);
+ *iscombo = true;
+ }
+ else
+ {
+ *iscombo = false;
+ }
+}
+
+/*
+ * Combo command ids are only interesting to the inserting and deleting
+ * transaction, so we can forget about them at the end of transaction.
+ */
+void
+AtEOXact_ComboCid(void)
+{
+ /*
+ * Don't bother to pfree. These are allocated in TopTransactionContext,
+ * so they're going to go away at the end of transaction anyway.
+ */
+ comboHash = NULL;
+
+ comboCids = NULL;
+ usedComboCids = 0;
+ sizeComboCids = 0;
+}
+
+
+/**** Internal routines ****/
+
+/*
+ * Get a combo command id that maps to cmin and cmax.
+ *
+ * We try to reuse old combo command ids when possible.
+ */
+static CommandId
+GetComboCommandId(CommandId cmin, CommandId cmax)
+{
+ CommandId combocid;
+ ComboCidKeyData key;
+ ComboCidEntry entry;
+ bool found;
+
+ /*
+ * Create the hash table and array the first time we need to use
+ * combo cids in the transaction.
+ */
+ if (comboHash == NULL)
+ {
+ HASHCTL hash_ctl;
+
+ memset(&hash_ctl, 0, sizeof(hash_ctl));
+ hash_ctl.keysize = sizeof(ComboCidKeyData);
+ hash_ctl.entrysize = sizeof(ComboCidEntryData);
+ hash_ctl.hash = tag_hash;
+ hash_ctl.hcxt = TopTransactionContext;
+
+ comboHash = hash_create("Combo CIDs",
+ CCID_HASH_SIZE,
+ &hash_ctl,
+ HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT);
+
+ comboCids = (ComboCidKeyData *)
+ MemoryContextAlloc(TopTransactionContext,
+ sizeof(ComboCidKeyData) * CCID_ARRAY_SIZE);
+ sizeComboCids = CCID_ARRAY_SIZE;
+ usedComboCids = 0;
+ }
+
+ /* Lookup or create a hash entry with the desired cmin/cmax */
+
+ /* We assume there is no struct padding in ComboCidKeyData! */
+ key.cmin = cmin;
+ key.cmax = cmax;
+ entry = (ComboCidEntry) hash_search(comboHash,
+ (void *) &key,
+ HASH_ENTER,
+ &found);
+
+ if (found)
+ {
+ /* Reuse an existing combo cid */
+ return entry->combocid;
+ }
+
+ /*
+ * We have to create a new combo cid. Check that there's room
+ * for it in the array, and grow it if there isn't.
+ */
+ if (usedComboCids >= sizeComboCids)
+ {
+ /* We need to grow the array */
+ int newsize = sizeComboCids * 2;
+
+ comboCids = (ComboCidKeyData *)
+ repalloc(comboCids, sizeof(ComboCidKeyData) * newsize);
+ sizeComboCids = newsize;
+ }
+
+ combocid = usedComboCids;
+
+ comboCids[combocid].cmin = cmin;
+ comboCids[combocid].cmax = cmax;
+ usedComboCids++;
+
+ entry->combocid = combocid;
+
+ return combocid;
+}
+
+static CommandId
+GetRealCmin(CommandId combocid)
+{
+ Assert(combocid < usedComboCids);
+ return comboCids[combocid].cmin;
+}
+
+static CommandId
+GetRealCmax(CommandId combocid)
+{
+ Assert(combocid < usedComboCids);
+ return comboCids[combocid].cmax;
+}
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/access/htup.h,v 1.90 2007/02/05 04:22:18 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/access/htup.h,v 1.91 2007/02/09 03:35:34 tgl Exp $
*
*-------------------------------------------------------------------------
*/
/*
* Heap tuple header. To avoid wasting space, the fields should be
- * layed out in such a way to avoid structure padding.
+ * laid out in such a way as to avoid structure padding.
*
* Datums of composite types (row types) share the same general structure
* as on-disk tuples, so that the same routines can be used to build and
* 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
- * and Cmin simultaneously, so this is no longer possible.
+ * 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 Cmin and Cmax
+ * are only interesting for the lifetime of the inserting and deleting
+ * transaction respectively. If a tuple is inserted and deleted in the same
+ * transaction, we store a "combo" command id that can be mapped to the real
+ * cmin and cmax, but only by use of local state within the originating
+ * backend. See combocid.c for more details. Meanwhile, Xvac is only set
+ * by VACUUM FULL, which does not have any command sub-structure and so does
+ * not need either Cmin or Cmax. (This requires that VACUUM FULL never try
+ * to move a tuple whose Cmin or Cmax is still interesting, ie, an insert-
+ * in-progress or delete-in-progress tuple.)
*
* A word about t_ctid: whenever a new tuple is stored on disk, its t_ctid
* is initialized with its own TID (location). If the tuple is ever updated,
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 */
+ CommandId t_cid; /* inserting or deleting command ID, or both */
TransactionId t_xvac; /* VACUUM FULL xact ID */
- } t_field4;
+ } t_field3;
} HeapTupleFields;
typedef struct DatumTupleFields
uint8 t_hoff; /* sizeof header incl. bitmap, padding */
- /* ^ - 27 bytes - ^ */
+ /* ^ - 23 bytes - ^ */
bits8 t_bits[1]; /* bitmap of NULLs -- VARIABLE LENGTH */
#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_COMBOCID 0x0020 /* t_cid is a combo 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 */
* FULL */
#define HEAP_MOVED (HEAP_MOVED_OFF | HEAP_MOVED_IN)
-#define HEAP_XACT_MASK 0xFFC0 /* visibility-related bits */
+#define HEAP_XACT_MASK 0xFFE0 /* visibility-related bits */
-/* information stored in t_infomask2, and accessor macros */
+/*
+ * information stored in t_infomask2:
+ */
#define HEAP_NATTS_MASK 0x7FF /* 11 bits for number of attributes */
-/* bits 0xF800 are unused */
-
-#define HeapTupleHeaderGetNatts(tup) ((tup)->t_infomask2 & HEAP_NATTS_MASK)
-#define HeapTupleHeaderSetNatts(tup, natts) \
-( \
- (tup)->t_infomask2 = ((tup)->t_infomask2 & ~HEAP_NATTS_MASK) | (natts) \
-)
+/* bits 0xF800 are currently unused */
/*
* HeapTupleHeader accessor macros
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.
+ * HeapTupleHeaderGetRawCommandId will give you what's in the header whether
+ * it is useful or not. Most code should use HeapTupleHeaderGetCmin or
+ * HeapTupleHeaderGetCmax instead, but note that those Assert that you can
+ * get a legitimate result, ie you are in the originating transaction!
*/
-#define HeapTupleHeaderGetCmax(tup) \
+#define HeapTupleHeaderGetRawCommandId(tup) \
( \
- (tup)->t_choice.t_heap.t_field4.t_cmax \
+ (tup)->t_choice.t_heap.t_field3.t_cid \
)
-#define HeapTupleHeaderSetCmax(tup, cid) \
+/* SetCmin is reasonably simple since we never need a combo CID */
+#define HeapTupleHeaderSetCmin(tup, cid) \
do { \
Assert(!((tup)->t_infomask & HEAP_MOVED)); \
- (tup)->t_choice.t_heap.t_field4.t_cmax = (cid); \
+ (tup)->t_choice.t_heap.t_field3.t_cid = (cid); \
+ (tup)->t_infomask &= ~HEAP_COMBOCID; \
+} while (0)
+
+/* SetCmax must be used after HeapTupleHeaderAdjustCmax; see combocid.c */
+#define HeapTupleHeaderSetCmax(tup, cid, iscombo) \
+do { \
+ Assert(!((tup)->t_infomask & HEAP_MOVED)); \
+ (tup)->t_choice.t_heap.t_field3.t_cid = (cid); \
+ if (iscombo) \
+ (tup)->t_infomask |= HEAP_COMBOCID; \
+ else \
+ (tup)->t_infomask &= ~HEAP_COMBOCID; \
} while (0)
#define HeapTupleHeaderGetXvac(tup) \
( \
((tup)->t_infomask & HEAP_MOVED) ? \
- (tup)->t_choice.t_heap.t_field4.t_xvac \
+ (tup)->t_choice.t_heap.t_field3.t_xvac \
: \
InvalidTransactionId \
)
#define HeapTupleHeaderSetXvac(tup, xid) \
do { \
Assert((tup)->t_infomask & HEAP_MOVED); \
- TransactionIdStore((xid), &(tup)->t_choice.t_heap.t_field4.t_xvac); \
+ TransactionIdStore((xid), &(tup)->t_choice.t_heap.t_field3.t_xvac); \
} while (0)
#define HeapTupleHeaderGetDatumLength(tup) \
*((Oid *) ((char *)(tup) + (tup)->t_hoff - sizeof(Oid))) = (oid); \
} while (0)
+#define HeapTupleHeaderGetNatts(tup) \
+ ((tup)->t_infomask2 & HEAP_NATTS_MASK)
+
+#define HeapTupleHeaderSetNatts(tup, natts) \
+( \
+ (tup)->t_infomask2 = ((tup)->t_infomask2 & ~HEAP_NATTS_MASK) | (natts) \
+)
+
/*
* BITMAPLEN(NATTS) -
* and thereby prevent accidental use of the nonexistent fields.
*
* MinimalTupleData contains a length word, some padding, and fields matching
- * HeapTupleHeaderData beginning with t_infomask2. The padding is chosen so that
- * offsetof(t_infomask2) is the same modulo MAXIMUM_ALIGNOF in both structs.
- * This makes data alignment rules equivalent in both cases.
+ * HeapTupleHeaderData beginning with t_infomask2. The padding is chosen so
+ * that offsetof(t_infomask2) is the same modulo MAXIMUM_ALIGNOF in both
+ * structs. This makes data alignment rules equivalent in both cases.
*
* When a minimal tuple is accessed via a HeapTupleData pointer, t_data is
* set to point MINIMAL_TUPLE_OFFSET bytes before the actual start of the
uint8 t_hoff; /* sizeof header incl. bitmap, padding */
- /* ^ - 27 bytes - ^ */
+ /* ^ - 23 bytes - ^ */
bits8 t_bits[1]; /* bitmap of NULLs -- VARIABLE LENGTH */
#define SizeOfHeapFreeze (offsetof(xl_heap_freeze, cutoff_xid) + sizeof(TransactionId))
+/* HeapTupleHeader functions implemented in utils/time/combocid.c */
+extern CommandId HeapTupleHeaderGetCmin(HeapTupleHeader tup);
+extern CommandId HeapTupleHeaderGetCmax(HeapTupleHeader tup);
+extern void HeapTupleHeaderAdjustCmax(HeapTupleHeader tup,
+ CommandId *cmax,
+ bool *iscombo);
+
#endif /* HTUP_H */
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.382 2007/02/07 23:11:29 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/catalog/catversion.h,v 1.383 2007/02/09 03:35:34 tgl Exp $
*
*-------------------------------------------------------------------------
*/
*/
/* yyyymmddN */
-#define CATALOG_VERSION_NO 200702071
+#define CATALOG_VERSION_NO 200702081
#endif
* Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
- * $PostgreSQL: pgsql/src/include/storage/bufpage.h,v 1.69 2007/01/05 22:19:57 momjian Exp $
+ * $PostgreSQL: pgsql/src/include/storage/bufpage.h,v 1.70 2007/02/09 03:35:34 tgl Exp $
*
*-------------------------------------------------------------------------
*/
/*
* Page layout version number 0 is for pre-7.3 Postgres releases.
* Releases 7.3 and 7.4 use 1, denoting a new HeapTupleHeader layout.
- * Release 8.0 changed the HeapTupleHeader layout again.
- * Release 8.1 redefined HeapTupleHeader infomask bits.
+ * Release 8.0 uses 2; it changed the HeapTupleHeader layout again.
+ * Release 8.1 uses 3; it redefined HeapTupleHeader infomask bits.
+ * Release 8.3 uses 4; it changed the HeapTupleHeader layout again.
*/
-#define PG_PAGE_LAYOUT_VERSION 3
+#define PG_PAGE_LAYOUT_VERSION 4
/* ----------------------------------------------------------------
--- /dev/null
+/*-------------------------------------------------------------------------
+ *
+ * combocid.h
+ * Combo command ID support routines
+ *
+ *
+ * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * $PostgreSQL: pgsql/src/include/utils/combocid.h,v 1.1 2007/02/09 03:35:34 tgl Exp $
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef COMBOCID_H
+#define COMBOCID_H
+
+/*
+ * HeapTupleHeaderGetCmin and HeapTupleHeaderGetCmax function prototypes
+ * are in access/htup.h, because that's where the macro definitions that
+ * those functions replaced used to be.
+ */
+
+extern void AtEOXact_ComboCid(void);
+
+#endif /* COMBOCID_H */
/**********************************************************************
* plperl.c - perl as a procedural language for PostgreSQL
*
- * $PostgreSQL: pgsql/src/pl/plperl/plperl.c,v 1.126 2007/02/01 19:10:29 momjian Exp $
+ * $PostgreSQL: pgsql/src/pl/plperl/plperl.c,v 1.127 2007/02/09 03:35:34 tgl Exp $
*
**********************************************************************/
{
char *proname;
TransactionId fn_xmin;
- CommandId fn_cmin;
+ ItemPointerData fn_tid;
bool fn_readonly;
bool lanpltrusted;
bool fn_retistuple; /* true, if function returns tuple */
*
* We start out by creating a "held" interpreter that we can use in
* trusted or untrusted mode (but not both) as the need arises. Later, we
- * assign that interpreter if it is available to either the trusted or
+ * assign that interpreter if it is available to either the trusted or
* untrusted interpreter. If it has already been assigned, and we need to
* create the other interpreter, we do that if we can, or error out.
* We detect if it is safe to run two interpreters during the setup of the
*/
-static void
+static void
check_interp(bool trusted)
{
if (interp_state == INTERP_HELD)
plperl_held_interp = NULL;
trusted_context = trusted;
}
- else if (interp_state == INTERP_BOTH ||
+ else if (interp_state == INTERP_BOTH ||
(trusted && interp_state == INTERP_TRUSTED) ||
(!trusted && interp_state == INTERP_UNTRUSTED))
{
}
else
{
- elog(ERROR,
+ elog(ERROR,
"cannot allocate second Perl interpreter on this platform");
-
}
-
}
elog(ERROR, "could not allocate Perl interpreter");
perl_construct(plperl_held_interp);
- perl_parse(plperl_held_interp, plperl_init_shared_libs,
+ perl_parse(plperl_held_interp, plperl_init_shared_libs,
3, embedding, NULL);
perl_run(plperl_held_interp);
SV *res;
res = eval_pv(TEST_FOR_MULTI,TRUE);
- can_run_two = SvIV(res);
+ can_run_two = SvIV(res);
interp_state = INTERP_HELD;
}
/************************************************************
* Lookup the internal proc name in the hashtable
************************************************************/
- hash_entry = hash_search(plperl_proc_hash, internal_proname,
+ hash_entry = hash_search(plperl_proc_hash, internal_proname,
HASH_FIND, NULL);
if (hash_entry)
* function's pg_proc entry without changing its OID.
************************************************************/
uptodate = (prodesc->fn_xmin == HeapTupleHeaderGetXmin(procTup->t_data) &&
- prodesc->fn_cmin == HeapTupleHeaderGetCmin(procTup->t_data));
+ ItemPointerEquals(&prodesc->fn_tid, &procTup->t_self));
if (!uptodate)
{
MemSet(prodesc, 0, sizeof(plperl_proc_desc));
prodesc->proname = strdup(internal_proname);
prodesc->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data);
- prodesc->fn_cmin = HeapTupleHeaderGetCmin(procTup->t_data);
+ prodesc->fn_tid = procTup->t_self;
/* Remember if function is STABLE/IMMUTABLE */
prodesc->fn_readonly =
PG_TRY();
{
/************************************************************
- * Resolve argument type names and then look them up by oid
- * in the system cache, and remember the required information
- * for input conversion.
+ * Resolve argument type names and then look them up by oid
+ * in the system cache, and remember the required information
+ * for input conversion.
************************************************************/
for (i = 0; i < argc; i++)
{
* free all memory before SPI_freeplan, so if it dies, nothing will be
* left over
*/
- hash_search(plperl_query_hash, query,
- HASH_REMOVE,NULL);
+ hash_search(plperl_query_hash, query,
+ HASH_REMOVE, NULL);
plan = qdesc->plan;
free(qdesc->argtypes);
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.112 2007/02/08 18:37:14 tgl Exp $
+ * $PostgreSQL: pgsql/src/pl/plpgsql/src/pl_comp.c,v 1.113 2007/02/09 03:35:34 tgl Exp $
*
*-------------------------------------------------------------------------
*/
{
/* We have a compiled function, but is it still valid? */
if (function->fn_xmin == HeapTupleHeaderGetXmin(procTup->t_data) &&
- function->fn_cmin == HeapTupleHeaderGetCmin(procTup->t_data))
+ ItemPointerEquals(&function->fn_tid, &procTup->t_self))
function_valid = true;
else
{
function->fn_name = pstrdup(NameStr(procStruct->proname));
function->fn_oid = fcinfo->flinfo->fn_oid;
function->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data);
- function->fn_cmin = HeapTupleHeaderGetCmin(procTup->t_data);
+ function->fn_tid = procTup->t_self;
function->fn_functype = functype;
function->fn_cxt = func_cxt;
function->out_param_varno = -1; /* set up for no OUT param */
*
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.84 2007/01/30 22:05:13 tgl Exp $
+ * $PostgreSQL: pgsql/src/pl/plpgsql/src/plpgsql.h,v 1.85 2007/02/09 03:35:34 tgl Exp $
*
*-------------------------------------------------------------------------
*/
char *fn_name;
Oid fn_oid;
TransactionId fn_xmin;
- CommandId fn_cmin;
+ ItemPointerData fn_tid;
int fn_functype;
PLpgSQL_func_hashkey *fn_hashkey; /* back-link to hashtable key */
MemoryContext fn_cxt;
/**********************************************************************
* plpython.c - python as a procedural language for PostgreSQL
*
- * $PostgreSQL: pgsql/src/pl/plpython/plpython.c,v 1.94 2007/02/01 19:10:30 momjian Exp $
+ * $PostgreSQL: pgsql/src/pl/plpython/plpython.c,v 1.95 2007/02/09 03:35:35 tgl Exp $
*
*********************************************************************
*/
char *proname; /* SQL name of procedure */
char *pyname; /* Python name of procedure */
TransactionId fn_xmin;
- CommandId fn_cmin;
+ ItemPointerData fn_tid;
bool fn_readonly;
PLyTypeInfo result; /* also used to store info for trigger tuple
* type */
elog(FATAL, "proc->me != plproc");
/* did we find an up-to-date cache entry? */
if (proc->fn_xmin != HeapTupleHeaderGetXmin(procTup->t_data) ||
- proc->fn_cmin != HeapTupleHeaderGetCmin(procTup->t_data))
+ !ItemPointerEquals(&proc->fn_tid, &procTup->t_self))
{
Py_DECREF(plproc);
proc = NULL;
proc->proname = PLy_strdup(NameStr(procStruct->proname));
proc->pyname = PLy_strdup(procName);
proc->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data);
- proc->fn_cmin = HeapTupleHeaderGetCmin(procTup->t_data);
+ proc->fn_tid = procTup->t_self;
/* Remember if function is STABLE/IMMUTABLE */
proc->fn_readonly =
(procStruct->provolatile != PROVOLATILE_VOLATILE);
* pltcl.c - PostgreSQL support for Tcl as
* procedural language (PL)
*
- * $PostgreSQL: pgsql/src/pl/tcl/pltcl.c,v 1.109 2007/02/01 19:10:30 momjian Exp $
+ * $PostgreSQL: pgsql/src/pl/tcl/pltcl.c,v 1.110 2007/02/09 03:35:35 tgl Exp $
*
**********************************************************************/
{
char *proname;
TransactionId fn_xmin;
- CommandId fn_cmin;
+ ItemPointerData fn_tid;
bool fn_readonly;
bool lanpltrusted;
FmgrInfo result_in_func;
prodesc = (pltcl_proc_desc *) Tcl_GetHashValue(hashent);
uptodate = (prodesc->fn_xmin == HeapTupleHeaderGetXmin(procTup->t_data) &&
- prodesc->fn_cmin == HeapTupleHeaderGetCmin(procTup->t_data));
+ ItemPointerEquals(&prodesc->fn_tid, &procTup->t_self));
if (!uptodate)
{
MemSet(prodesc, 0, sizeof(pltcl_proc_desc));
prodesc->proname = strdup(internal_proname);
prodesc->fn_xmin = HeapTupleHeaderGetXmin(procTup->t_data);
- prodesc->fn_cmin = HeapTupleHeaderGetCmin(procTup->t_data);
+ prodesc->fn_tid = procTup->t_self;
/* Remember if function is STABLE/IMMUTABLE */
prodesc->fn_readonly =
--- /dev/null
+--
+-- Tests for some likely failure cases with combo cmin/cmax mechanism
+--
+CREATE TEMP TABLE combocidtest (foobar int);
+BEGIN;
+-- a few dummy ops to push up the CommandId counter
+SELECT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+INSERT INTO combocidtest VALUES (1);
+INSERT INTO combocidtest VALUES (2);
+SELECT ctid,cmin,* FROM combocidtest;
+ ctid | cmin | foobar
+-------+------+--------
+ (0,1) | 10 | 1
+ (0,2) | 11 | 2
+(2 rows)
+
+SAVEPOINT s1;
+UPDATE combocidtest SET foobar = foobar + 10;
+-- here we should see only updated tuples
+SELECT ctid,cmin,* FROM combocidtest;
+ ctid | cmin | foobar
+-------+------+--------
+ (0,3) | 13 | 11
+ (0,4) | 13 | 12
+(2 rows)
+
+ROLLBACK TO s1;
+-- now we should see old tuples, but with combo CIDs starting at 0
+SELECT ctid,cmin,* FROM combocidtest;
+ ctid | cmin | foobar
+-------+------+--------
+ (0,1) | 0 | 1
+ (0,2) | 1 | 2
+(2 rows)
+
+COMMIT;
+-- combo data is not there anymore, but should still see tuples
+SELECT ctid,cmin,* FROM combocidtest;
+ ctid | cmin | foobar
+-------+------+--------
+ (0,1) | 0 | 1
+ (0,2) | 1 | 2
+(2 rows)
+
+-- Test combo cids with portals
+BEGIN;
+INSERT INTO combocidtest VALUES (333);
+DECLARE c CURSOR FOR SELECT ctid,cmin,* FROM combocidtest;
+DELETE FROM combocidtest;
+FETCH ALL FROM c;
+ ctid | cmin | foobar
+-------+------+--------
+ (0,1) | 2 | 1
+ (0,2) | 2 | 2
+ (0,5) | 0 | 333
+(3 rows)
+
+ROLLBACK;
+SELECT ctid,cmin,* FROM combocidtest;
+ ctid | cmin | foobar
+-------+------+--------
+ (0,1) | 2 | 1
+ (0,2) | 2 | 2
+(2 rows)
+
+-- check behavior with locked tuples
+BEGIN;
+-- a few dummy ops to push up the CommandId counter
+SELECT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+SELECT 1;
+ ?column?
+----------
+ 1
+(1 row)
+
+INSERT INTO combocidtest VALUES (444);
+SELECT ctid,cmin,* FROM combocidtest;
+ ctid | cmin | foobar
+-------+------+--------
+ (0,1) | 2 | 1
+ (0,2) | 2 | 2
+ (0,6) | 10 | 444
+(3 rows)
+
+SAVEPOINT s1;
+-- this doesn't affect cmin
+SELECT ctid,cmin,* FROM combocidtest FOR UPDATE;
+ ctid | cmin | foobar
+-------+------+--------
+ (0,1) | 2 | 1
+ (0,2) | 2 | 2
+ (0,6) | 10 | 444
+(3 rows)
+
+SELECT ctid,cmin,* FROM combocidtest;
+ ctid | cmin | foobar
+-------+------+--------
+ (0,1) | 2 | 1
+ (0,2) | 2 | 2
+ (0,6) | 10 | 444
+(3 rows)
+
+-- but this does
+UPDATE combocidtest SET foobar = foobar + 10;
+SELECT ctid,cmin,* FROM combocidtest;
+ ctid | cmin | foobar
+-------+------+--------
+ (0,7) | 14 | 11
+ (0,8) | 14 | 12
+ (0,9) | 14 | 454
+(3 rows)
+
+ROLLBACK TO s1;
+SELECT ctid,cmin,* FROM combocidtest;
+ ctid | cmin | foobar
+-------+------+--------
+ (0,1) | 14 | 1
+ (0,2) | 14 | 2
+ (0,6) | 0 | 444
+(3 rows)
+
+COMMIT;
+SELECT ctid,cmin,* FROM combocidtest;
+ ctid | cmin | foobar
+-------+------+--------
+ (0,1) | 14 | 1
+ (0,2) | 14 | 2
+ (0,6) | 0 | 444
+(3 rows)
+
-- This test tries to verify that WITHOUT OIDS actually saves space.
-- On machines where MAXALIGN is 8, WITHOUT OIDS may or may not save any
-- space, depending on the size of the tuple header + null bitmap.
--- As of 8.0 we need a 9-bit null bitmap to force the difference to appear.
+-- As of 8.3 we need a null bitmap of 8 or less bits for the difference
+-- to appear.
--
CREATE TABLE wi (i INT,
n1 int, n2 int, n3 int, n4 int,
- n5 int, n6 int, n7 int, n8 int) WITH OIDS;
+ n5 int, n6 int, n7 int) WITH OIDS;
CREATE TABLE wo (i INT,
n1 int, n2 int, n3 int, n4 int,
- n5 int, n6 int, n7 int, n8 int) WITHOUT OIDS;
+ n5 int, n6 int, n7 int) WITHOUT OIDS;
INSERT INTO wi VALUES (1); -- 1
INSERT INTO wo SELECT i FROM wi; -- 1
INSERT INTO wo SELECT i+1 FROM wi; -- 1+1=2
# ----------
# The first group of parallel test
-# $PostgreSQL: pgsql/src/test/regress/parallel_schedule,v 1.38 2007/01/28 16:16:54 neilc Exp $
+# $PostgreSQL: pgsql/src/test/regress/parallel_schedule,v 1.39 2007/02/09 03:35:35 tgl Exp $
# ----------
test: boolean char name varchar text int2 int4 int8 oid float4 float8 bit numeric uuid
# ----------
# The fifth group of parallel test
# ----------
-test: select_views portals_p2 rules foreign_key cluster dependency guc
+test: select_views portals_p2 rules foreign_key cluster dependency guc combocid
# ----------
# The sixth group of parallel test
-# $PostgreSQL: pgsql/src/test/regress/serial_schedule,v 1.36 2007/01/28 16:16:54 neilc Exp $
+# $PostgreSQL: pgsql/src/test/regress/serial_schedule,v 1.37 2007/02/09 03:35:35 tgl Exp $
# This should probably be in an order similar to parallel_schedule.
test: boolean
test: char
test: cluster
test: dependency
test: guc
+test: combocid
test: limit
test: plpgsql
test: copy2
--- /dev/null
+--
+-- Tests for some likely failure cases with combo cmin/cmax mechanism
+--
+CREATE TEMP TABLE combocidtest (foobar int);
+
+BEGIN;
+
+-- a few dummy ops to push up the CommandId counter
+SELECT 1;
+SELECT 1;
+SELECT 1;
+SELECT 1;
+SELECT 1;
+SELECT 1;
+SELECT 1;
+SELECT 1;
+SELECT 1;
+SELECT 1;
+
+INSERT INTO combocidtest VALUES (1);
+INSERT INTO combocidtest VALUES (2);
+
+SELECT ctid,cmin,* FROM combocidtest;
+
+SAVEPOINT s1;
+
+UPDATE combocidtest SET foobar = foobar + 10;
+
+-- here we should see only updated tuples
+SELECT ctid,cmin,* FROM combocidtest;
+
+ROLLBACK TO s1;
+
+-- now we should see old tuples, but with combo CIDs starting at 0
+SELECT ctid,cmin,* FROM combocidtest;
+
+COMMIT;
+
+-- combo data is not there anymore, but should still see tuples
+SELECT ctid,cmin,* FROM combocidtest;
+
+-- Test combo cids with portals
+BEGIN;
+
+INSERT INTO combocidtest VALUES (333);
+
+DECLARE c CURSOR FOR SELECT ctid,cmin,* FROM combocidtest;
+
+DELETE FROM combocidtest;
+
+FETCH ALL FROM c;
+
+ROLLBACK;
+
+SELECT ctid,cmin,* FROM combocidtest;
+
+-- check behavior with locked tuples
+BEGIN;
+
+-- a few dummy ops to push up the CommandId counter
+SELECT 1;
+SELECT 1;
+SELECT 1;
+SELECT 1;
+SELECT 1;
+SELECT 1;
+SELECT 1;
+SELECT 1;
+SELECT 1;
+SELECT 1;
+
+INSERT INTO combocidtest VALUES (444);
+
+SELECT ctid,cmin,* FROM combocidtest;
+
+SAVEPOINT s1;
+
+-- this doesn't affect cmin
+SELECT ctid,cmin,* FROM combocidtest FOR UPDATE;
+SELECT ctid,cmin,* FROM combocidtest;
+
+-- but this does
+UPDATE combocidtest SET foobar = foobar + 10;
+
+SELECT ctid,cmin,* FROM combocidtest;
+
+ROLLBACK TO s1;
+
+SELECT ctid,cmin,* FROM combocidtest;
+
+COMMIT;
+
+SELECT ctid,cmin,* FROM combocidtest;
-- This test tries to verify that WITHOUT OIDS actually saves space.
-- On machines where MAXALIGN is 8, WITHOUT OIDS may or may not save any
-- space, depending on the size of the tuple header + null bitmap.
--- As of 8.0 we need a 9-bit null bitmap to force the difference to appear.
+-- As of 8.3 we need a null bitmap of 8 or less bits for the difference
+-- to appear.
--
CREATE TABLE wi (i INT,
n1 int, n2 int, n3 int, n4 int,
- n5 int, n6 int, n7 int, n8 int) WITH OIDS;
+ n5 int, n6 int, n7 int) WITH OIDS;
CREATE TABLE wo (i INT,
n1 int, n2 int, n3 int, n4 int,
- n5 int, n6 int, n7 int, n8 int) WITHOUT OIDS;
+ n5 int, n6 int, n7 int) WITHOUT OIDS;
INSERT INTO wi VALUES (1); -- 1
INSERT INTO wo SELECT i FROM wi; -- 1