1 /*-------------------------------------------------------------------------
4 * Combo command ID support routines
6 * Before version 8.3, HeapTupleHeaderData had separate fields for cmin
7 * and cmax. To reduce the header size, cmin and cmax are now overlayed
8 * in the same field in the header. That usually works because you rarely
9 * insert and delete a tuple in the same transaction, and we don't need
10 * either field to remain valid after the originating transaction exits.
11 * To make it work when the inserting transaction does delete the tuple,
12 * we create a "combo" command ID and store that in the tuple header
13 * instead of cmin and cmax. The combo command ID can be mapped to the
14 * real cmin and cmax using a backend-private array, which is managed by
17 * To allow reusing existing combo cids, we also keep a hash table that
18 * maps cmin,cmax pairs to combo cids. This keeps the data structure size
19 * reasonable in most cases, since the number of unique pairs used by any
20 * one transaction is likely to be small.
22 * With a 32-bit combo command id we can represent 2^32 distinct cmin,cmax
23 * combinations. In the most perverse case where each command deletes a tuple
24 * generated by every previous command, the number of combo command ids
25 * required for N commands is N*(N+1)/2. That means that in the worst case,
26 * that's enough for 92682 commands. In practice, you'll run out of memory
27 * and/or disk space way before you reach that limit.
29 * The array and hash table are kept in TopTransactionContext, and are
30 * destroyed at the end of each transaction.
33 * Portions Copyright (c) 1996-2008, PostgreSQL Global Development Group
34 * Portions Copyright (c) 1994, Regents of the University of California
39 *-------------------------------------------------------------------------
44 #include "access/htup.h"
45 #include "access/xact.h"
46 #include "utils/combocid.h"
47 #include "utils/hsearch.h"
48 #include "utils/memutils.h"
51 /* Hash table to lookup combo cids by cmin and cmax */
52 static HTAB
*comboHash
= NULL
;
54 /* Key and entry structures for the hash table */
61 typedef ComboCidKeyData
*ComboCidKey
;
69 typedef ComboCidEntryData
*ComboCidEntry
;
71 /* Initial size of the hash table */
72 #define CCID_HASH_SIZE 100
76 * An array of cmin,cmax pairs, indexed by combo command id.
77 * To convert a combo cid to cmin and cmax, you do a simple array lookup.
79 static ComboCidKey comboCids
= NULL
;
80 static int usedComboCids
= 0; /* number of elements in comboCids */
81 static int sizeComboCids
= 0; /* allocated size of array */
83 /* Initial size of the array */
84 #define CCID_ARRAY_SIZE 100
87 /* prototypes for internal functions */
88 static CommandId
GetComboCommandId(CommandId cmin
, CommandId cmax
);
89 static CommandId
GetRealCmin(CommandId combocid
);
90 static CommandId
GetRealCmax(CommandId combocid
);
93 /**** External API ****/
96 * GetCmin and GetCmax assert that they are only called in situations where
97 * they make sense, that is, can deliver a useful answer. If you have
98 * reason to examine a tuple's t_cid field from a transaction other than
99 * the originating one, use HeapTupleHeaderGetRawCommandId() directly.
103 HeapTupleHeaderGetCmin(HeapTupleHeader tup
)
105 CommandId cid
= HeapTupleHeaderGetRawCommandId(tup
);
107 Assert(!(tup
->t_infomask
& HEAP_MOVED
));
108 Assert(TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tup
)));
110 if (tup
->t_infomask
& HEAP_COMBOCID
)
111 return GetRealCmin(cid
);
117 HeapTupleHeaderGetCmax(HeapTupleHeader tup
)
119 CommandId cid
= HeapTupleHeaderGetRawCommandId(tup
);
121 /* We do not store cmax when locking a tuple */
122 Assert(!(tup
->t_infomask
& (HEAP_MOVED
| HEAP_IS_LOCKED
)));
123 Assert(TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(tup
)));
125 if (tup
->t_infomask
& HEAP_COMBOCID
)
126 return GetRealCmax(cid
);
132 * Given a tuple we are about to delete, determine the correct value to store
133 * into its t_cid field.
135 * If we don't need a combo CID, *cmax is unchanged and *iscombo is set to
136 * FALSE. If we do need one, *cmax is replaced by a combo CID and *iscombo
139 * The reason this is separate from the actual HeapTupleHeaderSetCmax()
140 * operation is that this could fail due to out-of-memory conditions. Hence
141 * we need to do this before entering the critical section that actually
142 * changes the tuple in shared buffers.
145 HeapTupleHeaderAdjustCmax(HeapTupleHeader tup
,
150 * If we're marking a tuple deleted that was inserted by (any
151 * subtransaction of) our transaction, we need to use a combo command id.
152 * Test for HEAP_XMIN_COMMITTED first, because it's cheaper than a
153 * TransactionIdIsCurrentTransactionId call.
155 if (!(tup
->t_infomask
& HEAP_XMIN_COMMITTED
) &&
156 TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tup
)))
158 CommandId cmin
= HeapTupleHeaderGetCmin(tup
);
160 *cmax
= GetComboCommandId(cmin
, *cmax
);
170 * Combo command ids are only interesting to the inserting and deleting
171 * transaction, so we can forget about them at the end of transaction.
174 AtEOXact_ComboCid(void)
177 * Don't bother to pfree. These are allocated in TopTransactionContext, so
178 * they're going to go away at the end of transaction anyway.
188 /**** Internal routines ****/
191 * Get a combo command id that maps to cmin and cmax.
193 * We try to reuse old combo command ids when possible.
196 GetComboCommandId(CommandId cmin
, CommandId cmax
)
204 * Create the hash table and array the first time we need to use combo
205 * cids in the transaction.
207 if (comboHash
== NULL
)
211 memset(&hash_ctl
, 0, sizeof(hash_ctl
));
212 hash_ctl
.keysize
= sizeof(ComboCidKeyData
);
213 hash_ctl
.entrysize
= sizeof(ComboCidEntryData
);
214 hash_ctl
.hash
= tag_hash
;
215 hash_ctl
.hcxt
= TopTransactionContext
;
217 comboHash
= hash_create("Combo CIDs",
220 HASH_ELEM
| HASH_FUNCTION
| HASH_CONTEXT
);
222 comboCids
= (ComboCidKeyData
*)
223 MemoryContextAlloc(TopTransactionContext
,
224 sizeof(ComboCidKeyData
) * CCID_ARRAY_SIZE
);
225 sizeComboCids
= CCID_ARRAY_SIZE
;
229 /* Lookup or create a hash entry with the desired cmin/cmax */
231 /* We assume there is no struct padding in ComboCidKeyData! */
234 entry
= (ComboCidEntry
) hash_search(comboHash
,
241 /* Reuse an existing combo cid */
242 return entry
->combocid
;
246 * We have to create a new combo cid. Check that there's room for it in
247 * the array, and grow it if there isn't.
249 if (usedComboCids
>= sizeComboCids
)
251 /* We need to grow the array */
252 int newsize
= sizeComboCids
* 2;
254 comboCids
= (ComboCidKeyData
*)
255 repalloc(comboCids
, sizeof(ComboCidKeyData
) * newsize
);
256 sizeComboCids
= newsize
;
259 combocid
= usedComboCids
;
261 comboCids
[combocid
].cmin
= cmin
;
262 comboCids
[combocid
].cmax
= cmax
;
265 entry
->combocid
= combocid
;
271 GetRealCmin(CommandId combocid
)
273 Assert(combocid
< usedComboCids
);
274 return comboCids
[combocid
].cmin
;
278 GetRealCmax(CommandId combocid
)
280 Assert(combocid
< usedComboCids
);
281 return comboCids
[combocid
].cmax
;