PostgreSQL Source Code  git master
test_tidstore.c
Go to the documentation of this file.
1 /*--------------------------------------------------------------------------
2  *
3  * test_tidstore.c
4  * Test TidStore data structure.
5  *
6  * Note: all locking in this test module is useless since there is only
7  * a single process to use the TidStore. It is meant to be an example of
8  * usage.
9  *
10  * Copyright (c) 2024, PostgreSQL Global Development Group
11  *
12  * IDENTIFICATION
13  * src/test/modules/test_tidstore/test_tidstore.c
14  *
15  * -------------------------------------------------------------------------
16  */
17 #include "postgres.h"
18 
19 #include "access/tidstore.h"
20 #include "fmgr.h"
21 #include "funcapi.h"
22 #include "storage/block.h"
23 #include "storage/itemptr.h"
24 #include "storage/lwlock.h"
25 #include "utils/array.h"
26 #include "utils/memutils.h"
27 
29 
35 
36 static TidStore *tidstore = NULL;
37 static size_t tidstore_empty_size;
38 
39 /* array for verification of some tests */
40 typedef struct ItemArray
41 {
45  int max_tids;
46  int num_tids;
48 
50 
51 /* comparator routine for ItemPointer */
52 static int
53 itemptr_cmp(const void *left, const void *right)
54 {
55  BlockNumber lblk,
56  rblk;
57  OffsetNumber loff,
58  roff;
59 
61  rblk = ItemPointerGetBlockNumber((ItemPointer) right);
62 
63  if (lblk < rblk)
64  return -1;
65  if (lblk > rblk)
66  return 1;
67 
70 
71  if (loff < roff)
72  return -1;
73  if (loff > roff)
74  return 1;
75 
76  return 0;
77 }
78 
79 /*
80  * Create a TidStore. If shared is false, the tidstore is created
81  * on TopMemoryContext, otherwise on DSA. Although the tidstore
82  * is created on DSA, only the same process can subsequently use
83  * the tidstore. The tidstore handle is not shared anywhere.
84 */
85 Datum
87 {
88  bool shared = PG_GETARG_BOOL(0);
90 
91  /* doesn't really matter, since it's just a hint */
92  size_t tidstore_max_size = 2 * 1024 * 1024;
93  size_t array_init_size = 1024;
94 
95  Assert(tidstore == NULL);
96 
97  /*
98  * Create the TidStore on TopMemoryContext so that the same process use it
99  * for subsequent tests.
100  */
102 
103  if (shared)
104  {
105  int tranche_id;
106 
107  tranche_id = LWLockNewTrancheId();
108  LWLockRegisterTranche(tranche_id, "test_tidstore");
109 
110  tidstore = TidStoreCreateShared(tidstore_max_size, tranche_id);
111 
112  /*
113  * Remain attached until end of backend or explicitly detached so that
114  * the same process use the tidstore for subsequent tests.
115  */
117  }
118  else
119  /* VACUUM uses insert only, so we test the other option. */
120  tidstore = TidStoreCreateLocal(tidstore_max_size, false);
121 
123 
124  items.num_tids = 0;
125  items.max_tids = array_init_size / sizeof(ItemPointerData);
126  items.insert_tids = (ItemPointerData *) palloc0(array_init_size);
127  items.lookup_tids = (ItemPointerData *) palloc0(array_init_size);
128  items.iter_tids = (ItemPointerData *) palloc0(array_init_size);
129 
131 
132  PG_RETURN_VOID();
133 }
134 
135 static void
137 {
138  if (ARR_HASNULL(ta) && array_contains_nulls(ta))
139  ereport(ERROR,
140  (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
141  errmsg("array must not contain nulls")));
142 
143  if (ARR_NDIM(ta) > 1)
144  ereport(ERROR,
145  (errcode(ERRCODE_DATA_EXCEPTION),
146  errmsg("argument must be empty or one-dimensional array")));
147 }
148 
149 static void
151 {
152  if (tidstore == NULL)
153  elog(ERROR, "tidstore is not created");
154 }
155 
156 static void
158 {
159  int dst = 0;
160 
161  for (int src = 0; src < items.num_tids; src++)
162  if (ItemPointerGetBlockNumber(&items.insert_tids[src]) != blkno)
163  items.insert_tids[dst++] = items.insert_tids[src];
164  items.num_tids = dst;
165 }
166 
167 
168 /* Set the given block and offsets pairs */
169 Datum
171 {
172  BlockNumber blkno = PG_GETARG_INT64(0);
174  OffsetNumber *offs;
175  int noffs;
176 
178  sanity_check_array(ta);
179 
180  noffs = ArrayGetNItems(ARR_NDIM(ta), ARR_DIMS(ta));
181  offs = ((OffsetNumber *) ARR_DATA_PTR(ta));
182 
183  /* Set TIDs in the store */
185  TidStoreSetBlockOffsets(tidstore, blkno, offs, noffs);
187 
188  /* Remove the existing items of blkno from the verification array */
190 
191  /* Set TIDs in verification array */
192  for (int i = 0; i < noffs; i++)
193  {
194  ItemPointer tid;
195  int idx = items.num_tids + i;
196 
197  /* Enlarge the TID arrays if necessary */
198  if (idx >= items.max_tids)
199  {
200  items.max_tids *= 2;
204  }
205 
206  tid = &(items.insert_tids[idx]);
207  ItemPointerSet(tid, blkno, offs[i]);
208  }
209 
210  /* Update statistics */
211  items.num_tids += noffs;
212 
213  PG_RETURN_INT64(blkno);
214 }
215 
216 /*
217  * Verify TIDs in store against the array.
218  */
219 Datum
221 {
222  TidStoreIter *iter;
223  TidStoreIterResult *iter_result;
224  int num_iter_tids = 0;
225  int num_lookup_tids = 0;
226  BlockNumber prevblkno = 0;
227 
229 
230  /* lookup each member in the verification array */
231  for (int i = 0; i < items.num_tids; i++)
233  elog(ERROR, "missing TID with block %u, offset %u",
236 
237  /*
238  * Lookup all possible TIDs for each distinct block in the verification
239  * array and save successful lookups in the lookup array.
240  */
241 
242  for (int i = 0; i < items.num_tids; i++)
243  {
245 
246  if (i > 0 && blkno == prevblkno)
247  continue;
248 
249  for (OffsetNumber offset = FirstOffsetNumber; offset < MaxOffsetNumber; offset++)
250  {
251  ItemPointerData tid;
252 
253  ItemPointerSet(&tid, blkno, offset);
254 
256  if (TidStoreIsMember(tidstore, &tid))
257  ItemPointerSet(&items.lookup_tids[num_lookup_tids++], blkno, offset);
259  }
260 
261  prevblkno = blkno;
262  }
263 
264  /* Collect TIDs stored in the tidstore, in order */
265 
268  while ((iter_result = TidStoreIterateNext(iter)) != NULL)
269  {
270  OffsetNumber offsets[MaxOffsetNumber];
271  int num_offsets;
272 
273  num_offsets = TidStoreGetBlockOffsets(iter_result, offsets, lengthof(offsets));
274  Assert(num_offsets <= lengthof(offsets));
275  for (int i = 0; i < num_offsets; i++)
276  ItemPointerSet(&(items.iter_tids[num_iter_tids++]), iter_result->blkno,
277  offsets[i]);
278  }
279  TidStoreEndIterate(iter);
281 
282  /*
283  * Sort verification and lookup arrays and test that all arrays are the
284  * same.
285  */
286 
287  if (num_lookup_tids != items.num_tids)
288  elog(ERROR, "should have %d TIDs, have %d", items.num_tids, num_lookup_tids);
289  if (num_iter_tids != items.num_tids)
290  elog(ERROR, "should have %d TIDs, have %d", items.num_tids, num_iter_tids);
291 
294  for (int i = 0; i < items.num_tids; i++)
295  {
296  if (itemptr_cmp((const void *) &items.insert_tids[i], (const void *) &items.iter_tids[i]) != 0)
297  elog(ERROR, "TID iter array doesn't match verification array, got (%u,%u) expected (%u,%u)",
302  if (itemptr_cmp((const void *) &items.insert_tids[i], (const void *) &items.lookup_tids[i]) != 0)
303  elog(ERROR, "TID lookup array doesn't match verification array, got (%u,%u) expected (%u,%u)",
308  }
309 
310  PG_RETURN_VOID();
311 }
312 
313 /*
314  * In real world use, we care if the memory usage is greater than
315  * some configured limit. Here we just want to verify that
316  * TidStoreMemoryUsage is not broken.
317  */
318 Datum
320 {
321  bool is_full;
322 
324 
326 
327  PG_RETURN_BOOL(is_full);
328 }
329 
330 /* Free the tidstore */
331 Datum
333 {
335 
337  tidstore = NULL;
338  items.num_tids = 0;
342 
343  PG_RETURN_VOID();
344 }
Datum idx(PG_FUNCTION_ARGS)
Definition: _int_op.c:259
#define PG_GETARG_ARRAYTYPE_P_COPY(n)
Definition: array.h:264
#define ARR_NDIM(a)
Definition: array.h:290
#define ARR_DATA_PTR(a)
Definition: array.h:322
#define ARR_DIMS(a)
Definition: array.h:294
#define ARR_HASNULL(a)
Definition: array.h:291
bool array_contains_nulls(ArrayType *array)
Definition: arrayfuncs.c:3755
int ArrayGetNItems(int ndim, const int *dims)
Definition: arrayutils.c:57
uint32 BlockNumber
Definition: block.h:31
#define Assert(condition)
Definition: c.h:861
#define lengthof(array)
Definition: c.h:791
void dsa_pin_mapping(dsa_area *area)
Definition: dsa.c:635
int errcode(int sqlerrcode)
Definition: elog.c:853
int errmsg(const char *fmt,...)
Definition: elog.c:1070
#define ERROR
Definition: elog.h:39
#define elog(elevel,...)
Definition: elog.h:225
#define ereport(elevel,...)
Definition: elog.h:149
#define PG_RETURN_VOID()
Definition: fmgr.h:349
#define PG_RETURN_INT64(x)
Definition: fmgr.h:368
#define PG_GETARG_INT64(n)
Definition: fmgr.h:283
#define PG_GETARG_BOOL(n)
Definition: fmgr.h:274
#define PG_FUNCTION_ARGS
Definition: fmgr.h:193
#define PG_RETURN_BOOL(x)
Definition: fmgr.h:359
int i
Definition: isn.c:73
static void ItemPointerSet(ItemPointerData *pointer, BlockNumber blockNumber, OffsetNumber offNum)
Definition: itemptr.h:135
static OffsetNumber ItemPointerGetOffsetNumber(const ItemPointerData *pointer)
Definition: itemptr.h:124
static BlockNumber ItemPointerGetBlockNumber(const ItemPointerData *pointer)
Definition: itemptr.h:103
struct ItemPointerData ItemPointerData
void LWLockRegisterTranche(int tranche_id, const char *tranche_name)
Definition: lwlock.c:628
int LWLockNewTrancheId(void)
Definition: lwlock.c:603
void pfree(void *pointer)
Definition: mcxt.c:1521
MemoryContext TopMemoryContext
Definition: mcxt.c:149
void * palloc0(Size size)
Definition: mcxt.c:1347
void * repalloc(void *pointer, Size size)
Definition: mcxt.c:1541
uint16 OffsetNumber
Definition: off.h:24
#define FirstOffsetNumber
Definition: off.h:27
#define MaxOffsetNumber
Definition: off.h:28
#define qsort(a, b, c, d)
Definition: port.h:447
uintptr_t Datum
Definition: postgres.h:64
RT_SCOPE RT_RADIX_TREE *MemoryContext old_ctx
Definition: radixtree.h:1824
MemoryContextSwitchTo(old_ctx)
ItemPointerData * insert_tids
Definition: test_tidstore.c:42
ItemPointerData * lookup_tids
Definition: test_tidstore.c:43
ItemPointerData * iter_tids
Definition: test_tidstore.c:44
BlockNumber blkno
Definition: tidstore.h:29
struct ItemArray ItemArray
static void sanity_check_array(ArrayType *ta)
static void check_tidstore_available(void)
static int itemptr_cmp(const void *left, const void *right)
Definition: test_tidstore.c:53
PG_FUNCTION_INFO_V1(test_create)
PG_MODULE_MAGIC
Definition: test_tidstore.c:28
Datum do_set_block_offsets(PG_FUNCTION_ARGS)
Datum test_create(PG_FUNCTION_ARGS)
Definition: test_tidstore.c:86
static TidStore * tidstore
Definition: test_tidstore.c:36
static ItemArray items
Definition: test_tidstore.c:49
static void purge_from_verification_array(BlockNumber blkno)
Datum check_set_block_offsets(PG_FUNCTION_ARGS)
Datum test_destroy(PG_FUNCTION_ARGS)
Datum test_is_full(PG_FUNCTION_ARGS)
static size_t tidstore_empty_size
Definition: test_tidstore.c:37
void TidStoreEndIterate(TidStoreIter *iter)
Definition: tidstore.c:526
TidStoreIterResult * TidStoreIterateNext(TidStoreIter *iter)
Definition: tidstore.c:501
void TidStoreLockShare(TidStore *ts)
Definition: tidstore.c:302
TidStore * TidStoreCreateShared(size_t max_bytes, int tranche_id)
Definition: tidstore.c:210
void TidStoreDestroy(TidStore *ts)
Definition: tidstore.c:325
dsa_area * TidStoreGetDSA(TidStore *ts)
Definition: tidstore.c:552
void TidStoreUnlock(TidStore *ts)
Definition: tidstore.c:309
TidStore * TidStoreCreateLocal(size_t max_bytes, bool insert_only)
Definition: tidstore.c:162
bool TidStoreIsMember(TidStore *ts, ItemPointer tid)
Definition: tidstore.c:429
int TidStoreGetBlockOffsets(TidStoreIterResult *result, OffsetNumber *offsets, int max_offsets)
Definition: tidstore.c:574
TidStoreIter * TidStoreBeginIterate(TidStore *ts)
Definition: tidstore.c:479
void TidStoreLockExclusive(TidStore *ts)
Definition: tidstore.c:295
void TidStoreSetBlockOffsets(TidStore *ts, BlockNumber blkno, OffsetNumber *offsets, int num_offsets)
Definition: tidstore.c:353
size_t TidStoreMemoryUsage(TidStore *ts)
Definition: tidstore.c:540