From: Tom Lane Date: Fri, 9 Feb 2007 03:35:35 +0000 (+0000) Subject: Combine cmin and cmax fields of HeapTupleHeaders into a single field, by X-Git-Tag: REL8_3_BETA1~1269 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=c398300330cb3060d50652800dbd12729ab9f5ef;p=postgresql Combine cmin and cmax fields of HeapTupleHeaders into a single field, by keeping private state in each backend that has inserted and deleted the same tuple during its current top-level transaction. This is sufficient since there is no need to be able to determine the cmin/cmax from any other transaction. This gets us back down to 23-byte headers, removing a penalty paid in 8.0 to support subtransactions. Patch by Heikki Linnakangas, with minor revisions by moi, following a design hashed out awhile back on the pghackers list. --- diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c index 6200f953eb..470f48777d 100644 --- a/src/backend/access/common/heaptuple.c +++ b/src/backend/access/common/heaptuple.c @@ -16,7 +16,7 @@ * * * 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 $ * *------------------------------------------------------------------------- */ @@ -582,14 +582,18 @@ heap_getsysattr(HeapTuple tup, int attnum, TupleDesc tupleDesc, bool *isnull) 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); diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index 71ba6d2e52..d46308863f 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -8,7 +8,7 @@ * * * 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 @@ -1407,8 +1407,7 @@ heap_insert(Relation relation, HeapTuple tup, CommandId cid, 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); /* @@ -1585,6 +1584,7 @@ heap_delete(Relation relation, ItemPointer tid, PageHeader dp; Buffer buffer; bool have_tuple_lock = false; + bool iscombo; Assert(ItemPointerIsValid(tid)); @@ -1724,6 +1724,9 @@ l1: 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 */ @@ -1733,7 +1736,7 @@ l1: 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; @@ -1893,6 +1896,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup, Size newtupsize, pagefree; bool have_tuple_lock = false; + bool iscombo; Assert(ItemPointerIsValid(otid)); @@ -2058,8 +2062,13 @@ l2: 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 @@ -2088,7 +2097,7 @@ l2: 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; @@ -2183,7 +2192,7 @@ l2: 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 */ @@ -2687,12 +2696,11 @@ l3: /* * 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; @@ -3443,7 +3451,7 @@ heap_xlog_delete(XLogRecPtr lsn, XLogRecord *record) 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); @@ -3608,7 +3616,7 @@ heap_xlog_update(XLogRecPtr lsn, XLogRecord *record, bool move) 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; } @@ -3761,7 +3769,7 @@ heap_xlog_lock(XLogRecPtr lsn, XLogRecord *record) 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); diff --git a/src/backend/access/transam/xact.c b/src/backend/access/transam/xact.c index 26a3c5ddc2..ab8b5fd8b4 100644 --- a/src/backend/access/transam/xact.c +++ b/src/backend/access/transam/xact.c @@ -10,7 +10,7 @@ * * * 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 $ * *------------------------------------------------------------------------- */ @@ -38,11 +38,12 @@ #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" /* @@ -1628,6 +1629,7 @@ CommitTransaction(void) AtEOXact_Namespace(true); /* smgrcommit already done */ AtEOXact_Files(); + AtEOXact_ComboCid(); pgstat_clear_snapshot(); pgstat_count_xact_commit(); pgstat_report_txn_timestamp(0); @@ -1845,6 +1847,7 @@ PrepareTransaction(void) AtEOXact_Namespace(true); /* smgrcommit already done */ AtEOXact_Files(); + AtEOXact_ComboCid(); pgstat_clear_snapshot(); CurrentResourceOwner = NULL; @@ -1997,6 +2000,7 @@ AbortTransaction(void) AtEOXact_Namespace(false); smgrabort(); AtEOXact_Files(); + AtEOXact_ComboCid(); pgstat_clear_snapshot(); pgstat_count_xact_rollback(); pgstat_report_txn_timestamp(0); diff --git a/src/backend/utils/fmgr/fmgr.c b/src/backend/utils/fmgr/fmgr.c index 886883a6ae..c36d9fee13 100644 --- a/src/backend/utils/fmgr/fmgr.c +++ b/src/backend/utils/fmgr/fmgr.c @@ -8,7 +8,7 @@ * * * 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 $ * *------------------------------------------------------------------------- */ @@ -64,7 +64,7 @@ typedef struct /* 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; @@ -483,7 +483,7 @@ lookup_C_func(HeapTuple procedureTuple) 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 */ } @@ -521,7 +521,7 @@ record_C_func(HeapTuple procedureTuple, &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; } diff --git a/src/backend/utils/time/Makefile b/src/backend/utils/time/Makefile index 67d2bfd262..9cbe31f8a6 100644 --- a/src/backend/utils/time/Makefile +++ b/src/backend/utils/time/Makefile @@ -4,7 +4,7 @@ # 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 $ # #------------------------------------------------------------------------- @@ -12,12 +12,12 @@ subdir = src/backend/utils/time 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) diff --git a/src/backend/utils/time/combocid.c b/src/backend/utils/time/combocid.c new file mode 100644 index 0000000000..5ba76660fd --- /dev/null +++ b/src/backend/utils/time/combocid.c @@ -0,0 +1,282 @@ +/*------------------------------------------------------------------------- + * + * 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; +} diff --git a/src/include/access/htup.h b/src/include/access/htup.h index eecc6959e8..31de835d29 100644 --- a/src/include/access/htup.h +++ b/src/include/access/htup.h @@ -7,7 +7,7 @@ * 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 $ * *------------------------------------------------------------------------- */ @@ -45,7 +45,7 @@ /* * 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 @@ -65,17 +65,18 @@ * 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, @@ -103,14 +104,13 @@ 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 @@ -145,7 +145,7 @@ typedef struct HeapTupleHeaderData uint8 t_hoff; /* sizeof header incl. bitmap, padding */ - /* ^ - 27 bytes - ^ */ + /* ^ - 23 bytes - ^ */ bits8 t_bits[1]; /* bitmap of NULLs -- VARIABLE LENGTH */ @@ -163,7 +163,7 @@ typedef HeapTupleHeaderData *HeapTupleHeader; #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 */ @@ -180,17 +180,13 @@ typedef HeapTupleHeaderData *HeapTupleHeader; * 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 @@ -219,39 +215,40 @@ typedef HeapTupleHeaderData *HeapTupleHeader; 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 \ ) @@ -259,7 +256,7 @@ do { \ #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) \ @@ -306,6 +303,14 @@ do { \ *((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) - @@ -374,9 +379,9 @@ do { \ * 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 @@ -405,7 +410,7 @@ typedef struct MinimalTupleData uint8 t_hoff; /* sizeof header incl. bitmap, padding */ - /* ^ - 27 bytes - ^ */ + /* ^ - 23 bytes - ^ */ bits8 t_bits[1]; /* bitmap of NULLs -- VARIABLE LENGTH */ @@ -638,4 +643,11 @@ typedef struct xl_heap_freeze #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 */ diff --git a/src/include/catalog/catversion.h b/src/include/catalog/catversion.h index 9ebc4e9b9f..5bdda05960 100644 --- a/src/include/catalog/catversion.h +++ b/src/include/catalog/catversion.h @@ -37,7 +37,7 @@ * 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 $ * *------------------------------------------------------------------------- */ @@ -53,6 +53,6 @@ */ /* yyyymmddN */ -#define CATALOG_VERSION_NO 200702071 +#define CATALOG_VERSION_NO 200702081 #endif diff --git a/src/include/storage/bufpage.h b/src/include/storage/bufpage.h index 53ce47f068..abb51404a5 100644 --- a/src/include/storage/bufpage.h +++ b/src/include/storage/bufpage.h @@ -7,7 +7,7 @@ * 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 $ * *------------------------------------------------------------------------- */ @@ -132,10 +132,11 @@ typedef PageHeaderData *PageHeader; /* * 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 /* ---------------------------------------------------------------- diff --git a/src/include/utils/combocid.h b/src/include/utils/combocid.h new file mode 100644 index 0000000000..b9497a68a5 --- /dev/null +++ b/src/include/utils/combocid.h @@ -0,0 +1,25 @@ +/*------------------------------------------------------------------------- + * + * 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 */ diff --git a/src/pl/plperl/plperl.c b/src/pl/plperl/plperl.c index 0cf8c57a13..33027c53bd 100644 --- a/src/pl/plperl/plperl.c +++ b/src/pl/plperl/plperl.c @@ -1,7 +1,7 @@ /********************************************************************** * 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 $ * **********************************************************************/ @@ -41,7 +41,7 @@ typedef struct plperl_proc_desc { char *proname; TransactionId fn_xmin; - CommandId fn_cmin; + ItemPointerData fn_tid; bool fn_readonly; bool lanpltrusted; bool fn_retistuple; /* true, if function returns tuple */ @@ -296,7 +296,7 @@ _PG_init(void) * * 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 @@ -304,7 +304,7 @@ _PG_init(void) */ -static void +static void check_interp(bool trusted) { if (interp_state == INTERP_HELD) @@ -322,7 +322,7 @@ check_interp(bool trusted) 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)) { @@ -349,11 +349,9 @@ check_interp(bool trusted) } else { - elog(ERROR, + elog(ERROR, "cannot allocate second Perl interpreter on this platform"); - } - } @@ -425,7 +423,7 @@ plperl_init_interp(void) 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); @@ -434,7 +432,7 @@ plperl_init_interp(void) SV *res; res = eval_pv(TEST_FOR_MULTI,TRUE); - can_run_two = SvIV(res); + can_run_two = SvIV(res); interp_state = INTERP_HELD; } @@ -1430,7 +1428,7 @@ compile_plperl_function(Oid fn_oid, bool is_trigger) /************************************************************ * 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) @@ -1445,7 +1443,7 @@ compile_plperl_function(Oid fn_oid, bool is_trigger) * 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) { @@ -1485,7 +1483,7 @@ compile_plperl_function(Oid fn_oid, bool is_trigger) 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 = @@ -2128,9 +2126,9 @@ plperl_spi_prepare(char *query, int argc, SV **argv) 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++) { @@ -2523,8 +2521,8 @@ plperl_spi_freeplan(char *query) * 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); diff --git a/src/pl/plpgsql/src/pl_comp.c b/src/pl/plpgsql/src/pl_comp.c index c342ed3e85..44fae0f1b2 100644 --- a/src/pl/plpgsql/src/pl_comp.c +++ b/src/pl/plpgsql/src/pl_comp.c @@ -8,7 +8,7 @@ * * * 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 $ * *------------------------------------------------------------------------- */ @@ -165,7 +165,7 @@ recheck: { /* 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 { @@ -355,7 +355,7 @@ do_compile(FunctionCallInfo fcinfo, 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 */ diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h index dad5ba5bb4..9f29fcd5da 100644 --- a/src/pl/plpgsql/src/plpgsql.h +++ b/src/pl/plpgsql/src/plpgsql.h @@ -8,7 +8,7 @@ * * * 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 $ * *------------------------------------------------------------------------- */ @@ -547,7 +547,7 @@ typedef struct PLpgSQL_function 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; diff --git a/src/pl/plpython/plpython.c b/src/pl/plpython/plpython.c index cd0f701581..90a3f87b15 100644 --- a/src/pl/plpython/plpython.c +++ b/src/pl/plpython/plpython.c @@ -1,7 +1,7 @@ /********************************************************************** * 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 $ * ********************************************************************* */ @@ -123,7 +123,7 @@ typedef struct PLyProcedure 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 */ @@ -1100,7 +1100,7 @@ PLy_procedure_get(FunctionCallInfo fcinfo, Oid tgreloid) 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; @@ -1151,7 +1151,7 @@ PLy_procedure_create(FunctionCallInfo fcinfo, Oid tgreloid, 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); diff --git a/src/pl/tcl/pltcl.c b/src/pl/tcl/pltcl.c index 7611fa7cc7..0477fa8c56 100644 --- a/src/pl/tcl/pltcl.c +++ b/src/pl/tcl/pltcl.c @@ -2,7 +2,7 @@ * 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 $ * **********************************************************************/ @@ -76,7 +76,7 @@ typedef struct pltcl_proc_desc { char *proname; TransactionId fn_xmin; - CommandId fn_cmin; + ItemPointerData fn_tid; bool fn_readonly; bool lanpltrusted; FmgrInfo result_in_func; @@ -962,7 +962,7 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid) 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) { @@ -1004,7 +1004,7 @@ compile_pltcl_function(Oid fn_oid, Oid tgreloid) 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 = diff --git a/src/test/regress/expected/combocid.out b/src/test/regress/expected/combocid.out new file mode 100644 index 0000000000..14e45fe489 --- /dev/null +++ b/src/test/regress/expected/combocid.out @@ -0,0 +1,242 @@ +-- +-- 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) + diff --git a/src/test/regress/expected/without_oid.out b/src/test/regress/expected/without_oid.out index c32daf815d..cb2c0c0137 100644 --- a/src/test/regress/expected/without_oid.out +++ b/src/test/regress/expected/without_oid.out @@ -5,14 +5,15 @@ -- 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 diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index efc1f1fef6..096d2c1c7a 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -1,6 +1,6 @@ # ---------- # 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 @@ -69,7 +69,7 @@ test: misc # ---------- # 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 diff --git a/src/test/regress/serial_schedule b/src/test/regress/serial_schedule index 75e8b83138..d109dabdc2 100644 --- a/src/test/regress/serial_schedule +++ b/src/test/regress/serial_schedule @@ -1,4 +1,4 @@ -# $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 @@ -88,6 +88,7 @@ test: foreign_key test: cluster test: dependency test: guc +test: combocid test: limit test: plpgsql test: copy2 diff --git a/src/test/regress/sql/combocid.sql b/src/test/regress/sql/combocid.sql new file mode 100644 index 0000000000..3f30839b1f --- /dev/null +++ b/src/test/regress/sql/combocid.sql @@ -0,0 +1,93 @@ +-- +-- 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; diff --git a/src/test/regress/sql/without_oid.sql b/src/test/regress/sql/without_oid.sql index 1a10a8533d..9fbb454d4d 100644 --- a/src/test/regress/sql/without_oid.sql +++ b/src/test/regress/sql/without_oid.sql @@ -6,14 +6,15 @@ -- 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