Coverage Report

Created: 2025-07-03 06:49

/src/postgres/src/backend/utils/time/combocid.c
Line
Count
Source (jump to first uncovered line)
1
/*-------------------------------------------------------------------------
2
 *
3
 * combocid.c
4
 *    Combo command ID support routines
5
 *
6
 * Before version 8.3, HeapTupleHeaderData had separate fields for cmin
7
 * and cmax.  To reduce the header size, cmin and cmax are now overlaid
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
15
 * this module.
16
 *
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.
21
 *
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.
28
 *
29
 * The array and hash table are kept in TopTransactionContext, and are
30
 * destroyed at the end of each transaction.
31
 *
32
 *
33
 * Portions Copyright (c) 1996-2025, PostgreSQL Global Development Group
34
 * Portions Copyright (c) 1994, Regents of the University of California
35
 *
36
 * IDENTIFICATION
37
 *    src/backend/utils/time/combocid.c
38
 *
39
 *-------------------------------------------------------------------------
40
 */
41
42
#include "postgres.h"
43
44
#include "access/htup_details.h"
45
#include "access/xact.h"
46
#include "miscadmin.h"
47
#include "storage/shmem.h"
48
#include "utils/combocid.h"
49
#include "utils/hsearch.h"
50
#include "utils/memutils.h"
51
52
/* Hash table to lookup combo CIDs by cmin and cmax */
53
static HTAB *comboHash = NULL;
54
55
/* Key and entry structures for the hash table */
56
typedef struct
57
{
58
  CommandId cmin;
59
  CommandId cmax;
60
} ComboCidKeyData;
61
62
typedef ComboCidKeyData *ComboCidKey;
63
64
typedef struct
65
{
66
  ComboCidKeyData key;
67
  CommandId combocid;
68
} ComboCidEntryData;
69
70
typedef ComboCidEntryData *ComboCidEntry;
71
72
/* Initial size of the hash table */
73
0
#define CCID_HASH_SIZE      100
74
75
76
/*
77
 * An array of cmin,cmax pairs, indexed by combo command id.
78
 * To convert a combo CID to cmin and cmax, you do a simple array lookup.
79
 */
80
static ComboCidKey comboCids = NULL;
81
static int  usedComboCids = 0;  /* number of elements in comboCids */
82
static int  sizeComboCids = 0;  /* allocated size of array */
83
84
/* Initial size of the array */
85
0
#define CCID_ARRAY_SIZE     100
86
87
88
/* prototypes for internal functions */
89
static CommandId GetComboCommandId(CommandId cmin, CommandId cmax);
90
static CommandId GetRealCmin(CommandId combocid);
91
static CommandId GetRealCmax(CommandId combocid);
92
93
94
/**** External API ****/
95
96
/*
97
 * GetCmin and GetCmax assert that they are only called in situations where
98
 * they make sense, that is, can deliver a useful answer.  If you have
99
 * reason to examine a tuple's t_cid field from a transaction other than
100
 * the originating one, use HeapTupleHeaderGetRawCommandId() directly.
101
 */
102
103
CommandId
104
HeapTupleHeaderGetCmin(const HeapTupleHeaderData *tup)
105
0
{
106
0
  CommandId cid = HeapTupleHeaderGetRawCommandId(tup);
107
108
0
  Assert(!(tup->t_infomask & HEAP_MOVED));
109
0
  Assert(TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tup)));
110
111
0
  if (tup->t_infomask & HEAP_COMBOCID)
112
0
    return GetRealCmin(cid);
113
0
  else
114
0
    return cid;
115
0
}
116
117
CommandId
118
HeapTupleHeaderGetCmax(const HeapTupleHeaderData *tup)
119
0
{
120
0
  CommandId cid = HeapTupleHeaderGetRawCommandId(tup);
121
122
0
  Assert(!(tup->t_infomask & HEAP_MOVED));
123
124
  /*
125
   * Because GetUpdateXid() performs memory allocations if xmax is a
126
   * multixact we can't Assert() if we're inside a critical section. This
127
   * weakens the check, but not using GetCmax() inside one would complicate
128
   * things too much.
129
   */
130
0
  Assert(CritSectionCount > 0 ||
131
0
       TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetUpdateXid(tup)));
132
133
0
  if (tup->t_infomask & HEAP_COMBOCID)
134
0
    return GetRealCmax(cid);
135
0
  else
136
0
    return cid;
137
0
}
138
139
/*
140
 * Given a tuple we are about to delete, determine the correct value to store
141
 * into its t_cid field.
142
 *
143
 * If we don't need a combo CID, *cmax is unchanged and *iscombo is set to
144
 * false.  If we do need one, *cmax is replaced by a combo CID and *iscombo
145
 * is set to true.
146
 *
147
 * The reason this is separate from the actual HeapTupleHeaderSetCmax()
148
 * operation is that this could fail due to out-of-memory conditions.  Hence
149
 * we need to do this before entering the critical section that actually
150
 * changes the tuple in shared buffers.
151
 */
152
void
153
HeapTupleHeaderAdjustCmax(const HeapTupleHeaderData *tup,
154
              CommandId *cmax,
155
              bool *iscombo)
156
0
{
157
  /*
158
   * If we're marking a tuple deleted that was inserted by (any
159
   * subtransaction of) our transaction, we need to use a combo command id.
160
   * Test for HeapTupleHeaderXminCommitted() first, because it's cheaper
161
   * than a TransactionIdIsCurrentTransactionId call.
162
   */
163
0
  if (!HeapTupleHeaderXminCommitted(tup) &&
164
0
    TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetRawXmin(tup)))
165
0
  {
166
0
    CommandId cmin = HeapTupleHeaderGetCmin(tup);
167
168
0
    *cmax = GetComboCommandId(cmin, *cmax);
169
0
    *iscombo = true;
170
0
  }
171
0
  else
172
0
  {
173
0
    *iscombo = false;
174
0
  }
175
0
}
176
177
/*
178
 * Combo command ids are only interesting to the inserting and deleting
179
 * transaction, so we can forget about them at the end of transaction.
180
 */
181
void
182
AtEOXact_ComboCid(void)
183
0
{
184
  /*
185
   * Don't bother to pfree. These are allocated in TopTransactionContext, so
186
   * they're going to go away at the end of transaction anyway.
187
   */
188
0
  comboHash = NULL;
189
190
0
  comboCids = NULL;
191
0
  usedComboCids = 0;
192
0
  sizeComboCids = 0;
193
0
}
194
195
196
/**** Internal routines ****/
197
198
/*
199
 * Get a combo command id that maps to cmin and cmax.
200
 *
201
 * We try to reuse old combo command ids when possible.
202
 */
203
static CommandId
204
GetComboCommandId(CommandId cmin, CommandId cmax)
205
0
{
206
0
  CommandId combocid;
207
0
  ComboCidKeyData key;
208
0
  ComboCidEntry entry;
209
0
  bool    found;
210
211
  /*
212
   * Create the hash table and array the first time we need to use combo
213
   * cids in the transaction.
214
   */
215
0
  if (comboHash == NULL)
216
0
  {
217
0
    HASHCTL   hash_ctl;
218
219
    /* Make array first; existence of hash table asserts array exists */
220
0
    comboCids = (ComboCidKeyData *)
221
0
      MemoryContextAlloc(TopTransactionContext,
222
0
                 sizeof(ComboCidKeyData) * CCID_ARRAY_SIZE);
223
0
    sizeComboCids = CCID_ARRAY_SIZE;
224
0
    usedComboCids = 0;
225
226
0
    hash_ctl.keysize = sizeof(ComboCidKeyData);
227
0
    hash_ctl.entrysize = sizeof(ComboCidEntryData);
228
0
    hash_ctl.hcxt = TopTransactionContext;
229
230
0
    comboHash = hash_create("Combo CIDs",
231
0
                CCID_HASH_SIZE,
232
0
                &hash_ctl,
233
0
                HASH_ELEM | HASH_BLOBS | HASH_CONTEXT);
234
0
  }
235
236
  /*
237
   * Grow the array if there's not at least one free slot.  We must do this
238
   * before possibly entering a new hashtable entry, else failure to
239
   * repalloc would leave a corrupt hashtable entry behind.
240
   */
241
0
  if (usedComboCids >= sizeComboCids)
242
0
  {
243
0
    int     newsize = sizeComboCids * 2;
244
245
0
    comboCids = (ComboCidKeyData *)
246
0
      repalloc(comboCids, sizeof(ComboCidKeyData) * newsize);
247
0
    sizeComboCids = newsize;
248
0
  }
249
250
  /* Lookup or create a hash entry with the desired cmin/cmax */
251
252
  /* We assume there is no struct padding in ComboCidKeyData! */
253
0
  key.cmin = cmin;
254
0
  key.cmax = cmax;
255
0
  entry = (ComboCidEntry) hash_search(comboHash,
256
0
                    &key,
257
0
                    HASH_ENTER,
258
0
                    &found);
259
260
0
  if (found)
261
0
  {
262
    /* Reuse an existing combo CID */
263
0
    return entry->combocid;
264
0
  }
265
266
  /* We have to create a new combo CID; we already made room in the array */
267
0
  combocid = usedComboCids;
268
269
0
  comboCids[combocid].cmin = cmin;
270
0
  comboCids[combocid].cmax = cmax;
271
0
  usedComboCids++;
272
273
0
  entry->combocid = combocid;
274
275
0
  return combocid;
276
0
}
277
278
static CommandId
279
GetRealCmin(CommandId combocid)
280
0
{
281
0
  Assert(combocid < usedComboCids);
282
0
  return comboCids[combocid].cmin;
283
0
}
284
285
static CommandId
286
GetRealCmax(CommandId combocid)
287
0
{
288
0
  Assert(combocid < usedComboCids);
289
0
  return comboCids[combocid].cmax;
290
0
}
291
292
/*
293
 * Estimate the amount of space required to serialize the current combo CID
294
 * state.
295
 */
296
Size
297
EstimateComboCIDStateSpace(void)
298
0
{
299
0
  Size    size;
300
301
  /* Add space required for saving usedComboCids */
302
0
  size = sizeof(int);
303
304
  /* Add space required for saving ComboCidKeyData */
305
0
  size = add_size(size, mul_size(sizeof(ComboCidKeyData), usedComboCids));
306
307
0
  return size;
308
0
}
309
310
/*
311
 * Serialize the combo CID state into the memory, beginning at start_address.
312
 * maxsize should be at least as large as the value returned by
313
 * EstimateComboCIDStateSpace.
314
 */
315
void
316
SerializeComboCIDState(Size maxsize, char *start_address)
317
0
{
318
0
  char     *endptr;
319
320
  /* First, we store the number of currently-existing combo CIDs. */
321
0
  *(int *) start_address = usedComboCids;
322
323
  /* If maxsize is too small, throw an error. */
324
0
  endptr = start_address + sizeof(int) +
325
0
    (sizeof(ComboCidKeyData) * usedComboCids);
326
0
  if (endptr < start_address || endptr > start_address + maxsize)
327
0
    elog(ERROR, "not enough space to serialize ComboCID state");
328
329
  /* Now, copy the actual cmin/cmax pairs. */
330
0
  if (usedComboCids > 0)
331
0
    memcpy(start_address + sizeof(int), comboCids,
332
0
         (sizeof(ComboCidKeyData) * usedComboCids));
333
0
}
334
335
/*
336
 * Read the combo CID state at the specified address and initialize this
337
 * backend with the same combo CIDs.  This is only valid in a backend that
338
 * currently has no combo CIDs (and only makes sense if the transaction state
339
 * is serialized and restored as well).
340
 */
341
void
342
RestoreComboCIDState(char *comboCIDstate)
343
0
{
344
0
  int     num_elements;
345
0
  ComboCidKeyData *keydata;
346
0
  int     i;
347
0
  CommandId cid;
348
349
0
  Assert(!comboCids && !comboHash);
350
351
  /* First, we retrieve the number of combo CIDs that were serialized. */
352
0
  num_elements = *(int *) comboCIDstate;
353
0
  keydata = (ComboCidKeyData *) (comboCIDstate + sizeof(int));
354
355
  /* Use GetComboCommandId to restore each combo CID. */
356
0
  for (i = 0; i < num_elements; i++)
357
0
  {
358
0
    cid = GetComboCommandId(keydata[i].cmin, keydata[i].cmax);
359
360
    /* Verify that we got the expected answer. */
361
0
    if (cid != i)
362
0
      elog(ERROR, "unexpected command ID while restoring combo CIDs");
363
0
  }
364
0
}