diff options
Diffstat (limited to 'src/backend/utils/time/combocid.c')
-rw-r--r-- | src/backend/utils/time/combocid.c | 282 |
1 files changed, 282 insertions, 0 deletions
diff --git a/src/backend/utils/time/combocid.c b/src/backend/utils/time/combocid.c new file mode 100644 index 00000000000..5ba76660fdd --- /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; +} |