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-2025, 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 "storage/block.h"
22#include "storage/itemptr.h"
23#include "storage/lwlock.h"
24#include "utils/array.h"
25#include "utils/memutils.h"
26
28
34
35static TidStore *tidstore = NULL;
36static size_t tidstore_empty_size;
37
38/* array for verification of some tests */
39typedef struct ItemArray
40{
47
49
50/* comparator routine for ItemPointer */
51static int
52itemptr_cmp(const void *left, const void *right)
53{
54 BlockNumber lblk,
55 rblk;
56 OffsetNumber loff,
57 roff;
58
61
62 if (lblk < rblk)
63 return -1;
64 if (lblk > rblk)
65 return 1;
66
69
70 if (loff < roff)
71 return -1;
72 if (loff > roff)
73 return 1;
74
75 return 0;
76}
77
78/*
79 * Create a TidStore. If shared is false, the tidstore is created
80 * on TopMemoryContext, otherwise on DSA. Although the tidstore
81 * is created on DSA, only the same process can subsequently use
82 * the tidstore. The tidstore handle is not shared anywhere.
83*/
86{
87 bool shared = PG_GETARG_BOOL(0);
88 MemoryContext old_ctx;
89
90 /* doesn't really matter, since it's just a hint */
91 size_t tidstore_max_size = 2 * 1024 * 1024;
92 size_t array_init_size = 1024;
93
94 Assert(tidstore == NULL);
95
96 /*
97 * Create the TidStore on TopMemoryContext so that the same process use it
98 * for subsequent tests.
99 */
101
102 if (shared)
103 {
104 int tranche_id;
105
106 tranche_id = LWLockNewTrancheId();
107 LWLockRegisterTranche(tranche_id, "test_tidstore");
108
109 tidstore = TidStoreCreateShared(tidstore_max_size, tranche_id);
110
111 /*
112 * Remain attached until end of backend or explicitly detached so that
113 * the same process use the tidstore for subsequent tests.
114 */
116 }
117 else
118 /* VACUUM uses insert only, so we test the other option. */
119 tidstore = TidStoreCreateLocal(tidstore_max_size, false);
120
122
123 items.num_tids = 0;
124 items.max_tids = array_init_size / sizeof(ItemPointerData);
125 items.insert_tids = (ItemPointerData *) palloc0(array_init_size);
126 items.lookup_tids = (ItemPointerData *) palloc0(array_init_size);
127 items.iter_tids = (ItemPointerData *) palloc0(array_init_size);
128
129 MemoryContextSwitchTo(old_ctx);
130
132}
133
134static void
136{
137 if (ARR_HASNULL(ta) && array_contains_nulls(ta))
139 (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED),
140 errmsg("array must not contain nulls")));
141
142 if (ARR_NDIM(ta) > 1)
144 (errcode(ERRCODE_DATA_EXCEPTION),
145 errmsg("argument must be empty or one-dimensional array")));
146}
147
148static void
150{
151 if (tidstore == NULL)
152 elog(ERROR, "tidstore is not created");
153}
154
155static void
157{
158 int dst = 0;
159
160 for (int src = 0; src < items.num_tids; src++)
161 if (ItemPointerGetBlockNumber(&items.insert_tids[src]) != blkno)
162 items.insert_tids[dst++] = items.insert_tids[src];
163 items.num_tids = dst;
164}
165
166
167/* Set the given block and offsets pairs */
168Datum
170{
171 BlockNumber blkno = PG_GETARG_INT64(0);
173 OffsetNumber *offs;
174 int noffs;
175
178
179 noffs = ArrayGetNItems(ARR_NDIM(ta), ARR_DIMS(ta));
180 offs = ((OffsetNumber *) ARR_DATA_PTR(ta));
181
182 /* Set TIDs in the store */
184 TidStoreSetBlockOffsets(tidstore, blkno, offs, noffs);
186
187 /* Remove the existing items of blkno from the verification array */
189
190 /* Set TIDs in verification array */
191 for (int i = 0; i < noffs; i++)
192 {
193 ItemPointer tid;
194 int idx = items.num_tids + i;
195
196 /* Enlarge the TID arrays if necessary */
197 if (idx >= items.max_tids)
198 {
199 items.max_tids *= 2;
203 }
204
205 tid = &(items.insert_tids[idx]);
206 ItemPointerSet(tid, blkno, offs[i]);
207 }
208
209 /* Update statistics */
210 items.num_tids += noffs;
211
212 PG_RETURN_INT64(blkno);
213}
214
215/*
216 * Verify TIDs in store against the array.
217 */
218Datum
220{
221 TidStoreIter *iter;
222 TidStoreIterResult *iter_result;
223 int num_iter_tids = 0;
224 int num_lookup_tids = 0;
225 BlockNumber prevblkno = 0;
226
228
229 /* lookup each member in the verification array */
230 for (int i = 0; i < items.num_tids; i++)
232 elog(ERROR, "missing TID with block %u, offset %u",
235
236 /*
237 * Lookup all possible TIDs for each distinct block in the verification
238 * array and save successful lookups in the lookup array.
239 */
240
241 for (int i = 0; i < items.num_tids; i++)
242 {
244
245 if (i > 0 && blkno == prevblkno)
246 continue;
247
248 for (OffsetNumber offset = FirstOffsetNumber; offset < MaxOffsetNumber; offset++)
249 {
250 ItemPointerData tid;
251
252 ItemPointerSet(&tid, blkno, offset);
253
255 if (TidStoreIsMember(tidstore, &tid))
256 ItemPointerSet(&items.lookup_tids[num_lookup_tids++], blkno, offset);
258 }
259
260 prevblkno = blkno;
261 }
262
263 /* Collect TIDs stored in the tidstore, in order */
264
267 while ((iter_result = TidStoreIterateNext(iter)) != NULL)
268 {
270 int num_offsets;
271
272 num_offsets = TidStoreGetBlockOffsets(iter_result, offsets, lengthof(offsets));
273 Assert(num_offsets <= lengthof(offsets));
274 for (int i = 0; i < num_offsets; i++)
275 ItemPointerSet(&(items.iter_tids[num_iter_tids++]), iter_result->blkno,
276 offsets[i]);
277 }
278 TidStoreEndIterate(iter);
280
281 /*
282 * Sort verification and lookup arrays and test that all arrays are the
283 * same.
284 */
285
286 if (num_lookup_tids != items.num_tids)
287 elog(ERROR, "should have %d TIDs, have %d", items.num_tids, num_lookup_tids);
288 if (num_iter_tids != items.num_tids)
289 elog(ERROR, "should have %d TIDs, have %d", items.num_tids, num_iter_tids);
290
293 for (int i = 0; i < items.num_tids; i++)
294 {
296 elog(ERROR, "TID iter array doesn't match verification array, got (%u,%u) expected (%u,%u)",
302 elog(ERROR, "TID lookup array doesn't match verification array, got (%u,%u) expected (%u,%u)",
307 }
308
310}
311
312/*
313 * In real world use, we care if the memory usage is greater than
314 * some configured limit. Here we just want to verify that
315 * TidStoreMemoryUsage is not broken.
316 */
317Datum
319{
320 bool is_full;
321
323
325
326 PG_RETURN_BOOL(is_full);
327}
328
329/* Free the tidstore */
330Datum
332{
334
336 tidstore = NULL;
337 items.num_tids = 0;
341
343}
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:3767
int ArrayGetNItems(int ndim, const int *dims)
Definition: arrayutils.c:57
uint32 BlockNumber
Definition: block.h:31
#define Assert(condition)
Definition: c.h:815
#define lengthof(array)
Definition: c.h:745
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:72
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 * repalloc(void *pointer, Size size)
Definition: mcxt.c:1541
void pfree(void *pointer)
Definition: mcxt.c:1521
void * palloc0(Size size)
Definition: mcxt.c:1347
MemoryContext TopMemoryContext
Definition: mcxt.c:149
uint16 OffsetNumber
Definition: off.h:24
#define FirstOffsetNumber
Definition: off.h:27
#define MaxOffsetNumber
Definition: off.h:28
static MemoryContext MemoryContextSwitchTo(MemoryContext context)
Definition: palloc.h:124
#define qsort(a, b, c, d)
Definition: port.h:475
uintptr_t Datum
Definition: postgres.h:69
ItemPointerData * insert_tids
Definition: test_tidstore.c:41
ItemPointerData * lookup_tids
Definition: test_tidstore.c:42
ItemPointerData * iter_tids
Definition: test_tidstore.c:43
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:52
PG_FUNCTION_INFO_V1(test_create)
PG_MODULE_MAGIC
Definition: test_tidstore.c:27
Datum do_set_block_offsets(PG_FUNCTION_ARGS)
Datum test_create(PG_FUNCTION_ARGS)
Definition: test_tidstore.c:85
static TidStore * tidstore
Definition: test_tidstore.c:35
static ItemArray items
Definition: test_tidstore.c:48
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:36
dsa_area * TidStoreGetDSA(TidStore *ts)
Definition: tidstore.c:544
TidStoreIter * TidStoreBeginIterate(TidStore *ts)
Definition: tidstore.c:471
void TidStoreEndIterate(TidStoreIter *iter)
Definition: tidstore.c:518
TidStoreIterResult * TidStoreIterateNext(TidStoreIter *iter)
Definition: tidstore.c:493
void TidStoreLockShare(TidStore *ts)
Definition: tidstore.c:294
TidStore * TidStoreCreateLocal(size_t max_bytes, bool insert_only)
Definition: tidstore.c:162
void TidStoreDestroy(TidStore *ts)
Definition: tidstore.c:317
void TidStoreUnlock(TidStore *ts)
Definition: tidstore.c:301
bool TidStoreIsMember(TidStore *ts, ItemPointer tid)
Definition: tidstore.c:421
int TidStoreGetBlockOffsets(TidStoreIterResult *result, OffsetNumber *offsets, int max_offsets)
Definition: tidstore.c:566
void TidStoreLockExclusive(TidStore *ts)
Definition: tidstore.c:287
void TidStoreSetBlockOffsets(TidStore *ts, BlockNumber blkno, OffsetNumber *offsets, int num_offsets)
Definition: tidstore.c:345
size_t TidStoreMemoryUsage(TidStore *ts)
Definition: tidstore.c:532
TidStore * TidStoreCreateShared(size_t max_bytes, int tranche_id)
Definition: tidstore.c:208